JavaScript的“with”语句有合法用途吗?

388

Alan Storm的评论是对我关于with语句回答的回应,这让我开始思考。我很少找到使用这个特定语言功能的原因,并且从未考虑过它可能会引起麻烦。现在,我想知道如何有效地使用with,同时避免其缺点。

你在哪里发现with语句有用?


56
我从未使用过它。如果我假装它不存在,那么没有它的生活会更容易。 - Nosredna
7
或许它曾经有很多合理的用途,但现在已经无关紧要了。ES5 Strict 移除了 with,所以这个东西已经不存在了。 - Thomas Aylott
29
需要翻译的内容:Worth noting here that ES5 Strict is still optional.这里需要注意的是,ES5严格模式仍然是可选的 - Shog9
6
在 ES5 严格模式中,不是删除 'with',而是修改标准使得当 'with' 内没有找到变量时,任何赋值都会绑定到参数对象上,这样做是否更好呢? - JussiR
3
@JussiR:很可能会这样。但是这样做的问题在于,它可能会破坏旧版浏览器中的某些功能。 - Sune Rasmussen
显示剩余2条评论
33个回答

8

我认为显而易见的用途是作为快捷方式。比如,如果您正在初始化一个对象,您只需省略很多“ObjectName.”的输入。有点像Lisp的“with-slots”,它可以让您编写

(with-slots (foo bar) objectname
   "some code that accesses foo and bar"

这与编写以下内容相同

"some code that accesses (slot-value objectname 'foo) and (slot-value objectname 'bar)""

这是一个快捷方式,当您的语言允许“Objectname.foo”时,这一点更加明显。

2
很高兴看到LISP代码!我认为JavaScript中的“with”显然受到了Scheme语言的启发,但遗憾的是,您因在JavaScript问题上发布LISP而被投票降低了。 - Fire Crow
2
基本原则上点赞。'with' 是一个极其强大的结构。但是大多数 JS 开发者不理解闭包,并在 JS 上编写荒谬复杂的 Java 类继承系统——那么他们怎么能意识到 'with' 提供的元编程潜力呢? - jared
当然,with-slots 要求您指定要使用哪些 slot,而 with 将使用在运行时绑定的任何 slot。 - Samuel Edwin Ward

7
使用 "with" 可以使你的代码更加简洁。
考虑下面的代码:
var photo = document.getElementById('photo');
photo.style.position = 'absolute';
photo.style.left = '10px';
photo.style.top = '10px';

您可以将其干燥为以下内容:
with(document.getElementById('photo').style) {
  position = 'absolute';
  left = '10px';
  top = '10px';
}

我想这取决于你是更注重易读性还是表达性。

第一个例子更易读,对于大多数代码来说可能更推荐。但是大多数代码本身就很平淡无奇。第二个例子有点更加晦涩,但是利用了语言的表达性质量来减少代码大小和不必要的变量。

我想喜欢Java或C#的人会选择第一种方式(object.member),而喜欢Ruby或Python的人则会选择后者。


糟糕,我没有意识到一年前已经有人发布了基本相同的示例。除了性能问题之外,“with”使代码更加DRY,但代价是稍微难以阅读。我认为,在与其他开发人员合作或大多数生产代码方面,避免使用“with”关键字是一个好习惯。但是,如果您正在与专业级程序员合作并且知道如何避免潜在的低效率问题,那么请尽情使用“with”。 - Jonah

6
有使用Delphi的经验,我认为使用with应该是一种最后的优化手段,可能由某种具有静态代码分析功能的javascript缩小器算法执行以验证其安全性。
如果大量使用with语句,你可能会遇到作用域问题,这可能会让你痛苦不堪,我不希望任何人在调试会话中找出代码中发生了什么事情,只是发现它捕获了一个对象成员或错误的本地变量,而不是你想要的全局或外部作用域变量。
VB with语句更好,因为它需要点来消除歧义,但是Delphi with语句就像是一把带有发射扳机的危险武器,对我来说,看起来javascript的with语句也足够相似,需要同样的警告。

5
JavaScript中的with语句比Delphi中的更差。在Delphi中,使用with关键字和对象.成员表示法的性能基本相同,甚至有时更快。而在JavaScript中,with语句需要遍历作用域链以查找匹配的成员,因此始终比对象.成员表示法慢。 - Martijn

5

不建议使用with,并且在ECMAScript 5严格模式下是禁止的。推荐的替代方法是将你想要访问其属性的对象分配给一个临时变量。

来源:Mozilla.org


4

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语句在IT技术中非常有用,如果您想修改作用域,那么这是必要的,因为您可以在运行时操纵自己的全局作用域。您可以将常量放在其中,或者某些常用的辅助函数,例如“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语句的方式中,它对性能没有负面影响,反而有好处,因为它可以减小代码大小,从而影响移动设备的内存使用。


3

对于一些短代码片段,我想使用三角函数,如sincos等,以度数模式而不是弧度模式。为此,我使用一个AngularDegree对象:

AngularDegree = new function() {
this.CONV = Math.PI / 180;
this.sin = function(x) { return Math.sin( x * this.CONV ) };
this.cos = function(x) { return Math.cos( x * this.CONV ) };
this.tan = function(x) { return Math.tan( x * this.CONV ) };
this.asin = function(x) { return Math.asin( x ) / this.CONV };
this.acos = function(x) { return Math.acos( x ) / this.CONV };
this.atan = function(x) { return Math.atan( x ) / this.CONV };
this.atan2 = function(x,y) { return Math.atan2(x,y) / this.CONV };
};

然后我可以在with块中使用角度模式的三角函数而不需要更多的语言噪音:

function getAzimut(pol,pos) {
  ...
  var d = pos.lon - pol.lon;
  with(AngularDegree) {
    var z = atan2( sin(d), cos(pol.lat)*tan(pos.lat) - sin(pol.lat)*cos(d) );
    return z;
    }
  }

这意味着:我使用一个对象作为函数的集合,我在有限的代码区域内启用它们以进行直接访问。我认为这很有用。

在编程中,使用with语句并不是一个好主意,因为这会使代码难以阅读。你无法确定哪个函数是全局的,哪个函数是在with对象的作用域内调用的。如果任何函数在对象作用域内未被定义,那么它将尝试在全局命名空间中访问该函数。 - Saket Patel
尽管我意识到作用域问题,但我仍然觉得这很有用。数学家阅读代码时希望直接看到上述公式是球面三角法中“四个连续部分定理”的应用。严格的替代方案使公式变得晦涩难懂:z = Math.atan2( Math.sin(d * Math.PI / 180), Math.cos( pol.lat * Math.PI / 180) * Math.tan( pos.lat * Math.PI / 180 ) - Math.sin( pol.lat * Math.PI / 180 ) * Math.cos( d * Math.PI / 180) ) * 180 / Math.PI; 可以得出相同的结果,但这太可怕了。 - rplantiko
需要记住的是,大多数人都不感到舒适。因此,除非你正在编写其他人永远不会接触的代码。另外,我很确定我可以向你展示一些使用with的例子,这可能会让你困惑。 - Ruan Mendes

3
我创建了一个“合并”函数,使用with语句可以消除某些不确定性:
if (typeof Object.merge !== 'function') {
    Object.merge = function (o1, o2) { // Function to merge all of the properties from one object into another
        for(var i in o2) { o1[i] = o2[i]; }
        return o1;
    };
}

我可以像使用with一样使用它,但我知道它不会影响我不想影响的任何范围。

用法:

var eDiv = document.createElement("div");
var eHeader = Object.merge(eDiv.cloneNode(false), {className: "header", onclick: function(){ alert("Click!"); }});
function NewObj() {
    Object.merge(this, {size: 4096, initDate: new Date()});
}

3
我认为对象字面量的使用很有趣,就像使用闭包的替代品。
for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       (function(info)
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       })(data[i]);
}

使用闭包的with语句等效方式
for(var i = nodes.length; i--;)
{
       // info is namespaced in a closure the click handler can access!
       with({info: data[i]})
       {           
            nodes[i].onclick = function(){ showStuff(info) };
       }        
}

我认为真正的风险是意外操纵不属于with语句的变量,这就是为什么我喜欢通过传入对象字面量到with语句来添加上下文信息,你可以清楚地看到代码中添加上下文信息后的结果。


3

将在相对复杂的环境中运行的代码放入容器中是一个不错的选择:我使用它来创建本地绑定,使得“window”等可以运行面向Web浏览器的代码。


3

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


5
过早优化。在计算过开销之前不要轻易断言代码较慢;现代和古老的JS实现可能都会对性能影响微乎其微。 - mk.
2
我强烈不同意你的结论,因为这可能并非只是你一个开发者面临的问题。 - Dave Van den Eynde
4
这段话是关于 JavaScript 中的代码优化问题。第一段代码使用了 with 关键字,而第二段代码则没有使用这个关键字。在执行循环语句时,第一段代码比第二段代码慢了约三倍。 - yorick
3
当我看到这个时,我在 Chrome 23 的控制台中运行了这些代码。我得到的结果是使用 with 关键字的代码为 1138,而不使用 with 为 903。由于这个微小的差异即使在紧密循环中也不会产生太大影响,因此在关注性能之前,我会根据编码简单性和重构易用性来考虑每种情况并作出选择。 - Plynx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接