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

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


当前回答

下面是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");

其他回答

有使用Delphi的经验,我会说使用with应该是最后的大小优化方法,可能由某种javascript最小化算法执行,并访问静态代码分析以验证其安全性。

随意使用with语句可能会遇到范围问题,这是一个非常痛苦的问题,我不希望任何人经历调试会话来弄清楚他…,却发现它捕获了一个对象成员或错误的局部变量,而不是您期望的全局变量或外部作用域变量。

VB的with语句更好,因为它需要点来消除范围的歧义,但Delphi的with语句是一把上膛的枪,带有一发之机,在我看来,javascript的with语句与之相似,足以保证同样的警告。

with语句可用于减少代码大小或用于私有类成员,例如:

// demo class framework
var Class= function(name, o) {
   var c=function(){};
   if( o.hasOwnProperty("constructor") ) {
       c= o.constructor;
   }
   delete o["constructor"];
   delete o["prototype"];
   c.prototype= {};
   for( var k in o ) c.prototype[k]= o[k];
   c.scope= Class.scope;
   c.scope.Class= c;
   c.Name= name;
   return c;
}
Class.newScope= function() {
    Class.scope= {};
    Class.scope.Scope= Class.scope;
    return Class.scope;
}

// create a new class
with( Class.newScope() ) {
   window.Foo= Class("Foo",{
      test: function() {
          alert( Class.Name );
      }
   });
}
(new Foo()).test();

如果你想要修改作用域,with语句是非常有用的,这对于拥有自己的全局作用域是必要的,可以在运行时操作。你可以在它上面放置常量或者某些常用的辅助函数,比如:“toUpper”,“toLower”或“isNumber”,“clipNumber”麻生..

关于糟糕的性能,我经常读到:作用域函数不会对性能产生任何影响,事实上,在我的FF中,有作用域的函数比无作用域的函数运行得更快:

var o={x: 5},r, fnRAW= function(a,b){ return a*b; }, fnScoped, s, e, i;
with( o ) {
    fnScoped= function(a,b){ return a*b; };
}

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnRAW(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

s= Date.now();
r= 0;
for( i=0; i < 1000000; i++ ) {
    r+= fnScoped(i,i);
}
e= Date.now();
console.log( (e-s)+"ms" );

所以在上面提到的方式中使用with语句对性能没有负面影响,但它减少了代码大小,这影响了移动设备上的内存使用。

我从来没有使用过,没有理由,也不推荐它。

with的问题是它阻止了ECMAScript实现可以执行的大量词汇优化。随着基于jit的快速引擎的兴起,这个问题在不久的将来可能会变得更加重要。

它可能看起来像允许更清晰的结构(例如,引入一个新的作用域而不是公共的匿名函数包装器或替换冗长的别名),但实际上不值得这样做。除了性能下降之外,还存在给错误对象的属性赋值的危险(当在注入作用域中的对象上找不到属性时),以及可能错误地引入全局变量。IIRC,后一个问题是促使Crockford建议避免的问题。

正如Andy E在Shog9回答的评论中指出的那样,当with与对象文字一起使用时,会发生这种潜在的意想不到的行为:

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with ({num: i}) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "[object Object]"
  }
}

并不是说意想不到的行为不是他的标志。

如果您仍然想使用这种技术,至少使用一个具有空原型的对象。

function scope(o) {
  var ret = Object.create(null);
  if (typeof o !== 'object') return ret;
  Object.keys(o).forEach(function (key) {
    ret[key] = o[key];
  });
  return ret;
}

for (var i = 0; i < 3; i++) {
  function toString() {
    return 'a';
  }
  with (scope({num: i})) {
    setTimeout(function() { console.log(num); }, 10);
    console.log(toString()); // prints "a"
  }
}

但这只适用于ES5+。也不要用with。

正如我之前的评论所指出的,我不认为你可以安全地使用它,无论它在任何给定的情况下有多么诱人。由于这里没有直接涉及到这个问题,我将重复一遍。考虑以下代码

user = {};
someFunctionThatDoesStuffToUser(user);
someOtherFunction(user);

with(user){
    name = 'Bob';
    age  = 20;
}

如果不仔细研究这些函数调用,就无法知道这段代码运行后程序的状态是什么。如果user.name已经设置,它现在将是Bob。如果没有设置,全局名称将被初始化或更改为Bob,用户对象将保持没有name属性。

错误发生。如果你使用with,你最终会这样做,增加你的程序失败的机会。更糟糕的是,您可能会遇到在with块中设置全局变量的工作代码,或者是故意的,或者是作者不知道这个构造的怪处。这很像在开关上遇到了故障,你不知道作者是否有意这样做,也没有办法知道“修复”代码是否会引入回归。

现代编程语言充满了特性。一些特性,在使用多年后,被发现是不好的,应该避免使用。Javascript的with就是其中之一。