Alan Storm对我关于with声明的回答的评论引起了我的思考。我很少找到使用这个特殊语言特性的理由,也从来没有想过它可能会带来什么麻烦。现在,我很好奇如何有效地利用with,同时避免它的陷阱。

你觉得with语句在哪里有用?


当前回答

使用with还会使代码在许多实现中变慢,因为现在所有内容都被包装在一个额外的查找范围中。在JavaScript中使用with没有合理的理由。

其他回答

你可以定义一个小的帮助函数来提供with的好处,而不会产生歧义:

var with_ = function (obj, func) { func (obj); };

with_ (object_name_here, function (_)
{
    _.a = "foo";
    _.b = "bar";
});

CoffeeScript的Coco fork有一个with关键字,但它只是简单地将this(在CoffeeScript/Coco中也可以写成@)设置为块中的目标对象。这消除了歧义,实现了ES5严格模式兼容:

with long.object.reference
  @a = 'foo'
  bar = @b

今天我想到了另一个用法,所以我兴奋地在网上搜索了一下,发现了一个已有的提及:在块作用域内定义变量。

背景

尽管JavaScript表面上与C和c++相似,但它并不将变量作用于定义变量的块:

var name = "Joe";
if ( true )
{
   var name = "Jack";
}
// name now contains "Jack"

在循环中声明闭包是一个常见的任务,这可能会导致错误:

for (var i=0; i<3; ++i)
{
   var num = i;
   setTimeout(function() { alert(num); }, 10);
}

因为for循环没有引入新的作用域,所以相同的num(值为2)将由所有三个函数共享。

一个新的范围:let and with

随着ES6中let语句的引入,在必要时引入一个新的作用域就变得容易了,以避免这些问题:

// variables introduced in this statement 
// are scoped to each iteration of the loop
for (let i=0; i<3; ++i)
{
   setTimeout(function() { alert(i); }, 10);
}

甚至:

for (var i=0; i<3; ++i)
{
   // variables introduced in this statement 
   // are scoped to the block containing it.
   let num = i;
   setTimeout(function() { alert(num); }, 10);
}

在ES6普及之前,这种使用仍然局限于最新的浏览器和愿意使用编译器的开发人员。然而,我们可以很容易地模拟这种行为使用:

for (var i=0; i<3; ++i)
{
   // object members introduced in this statement 
   // are scoped to the block following it.
   with ({num: i})
   {
      setTimeout(function() { alert(num); }, 10);
   }
}

循环现在按预期工作,创建了三个值从0到2的独立变量。注意,在块内声明的变量不局限于它,这与c++中块的行为不同(在C中,变量必须在块的开始声明,所以在某种程度上是类似的)。这种行为实际上非常类似于早期版本的Mozilla浏览器中引入的let块语法,但在其他地方没有被广泛采用。

似乎不值得,因为你可以做到以下几点:

var o = incrediblyLongObjectNameThatNoOneWouldUse;
o.name = "Bob";
o.age = "50";

下面是with的一个很好的用法:根据存储在Object中的值向Object Literal添加新元素。这是我今天用的一个例子:

我有一组可能使用的瓷砖(面向顶部、底部、左侧或右侧),我想快速添加一个瓷砖列表,在游戏开始时始终放置并锁定这些瓷砖。我不想一直输入类型。tbr用于列表中的每种类型,所以我只使用with。

Tile.types = (function(t,l,b,r) {
  function j(a) { return a.join(' '); }
  // all possible types
  var types = { 
    br:  j(  [b,r]),
    lbr: j([l,b,r]),
    lb:  j([l,b]  ),  
    tbr: j([t,b,r]),
    tbl: j([t,b,l]),
    tlr: j([t,l,r]),
    tr:  j([t,r]  ),  
    tl:  j([t,l]  ),  
    locked: []
  };  
  // store starting (base/locked) tiles in types.locked
  with( types ) { locked = [ 
    br,  lbr, lbr, lb, 
    tbr, tbr, lbr, tbl,
    tbr, tlr, tbl, tbl,
    tr,  tlr, tlr, tl
  ] } 
  return types;
})("top","left","bottom","right");