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

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


当前回答

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

背景

尽管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块语法,但在其他地方没有被广泛采用。

其他回答

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

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

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

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

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

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

使用“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的人会选择后者。

你可以在W3schools http://www.w3schools.com/js/js_form_validation.asp上看到javascript表单的验证,其中对象表单被“扫描”,以找到名称为“email”的输入

但我已经修改了它,从任何形式的所有字段验证为不空,无论名称或数量的字段在一个形式。我只测试了文本字段。

但是with()使事情变得更简单。代码如下:

function validate_required(field)
{
with (field)
  {
  if (value==null||value=="")
    {
    alert('All fields are mandtory');return false;
    }
  else
    {
    return true;
    }
  }
}

function validate_form(thisform)
{
with (thisform)
  {
    for(fiie in elements){
        if (validate_required(elements[fiie])==false){
            elements[fiie].focus();
            elements[fiie].style.border='1px solid red';
            return false;
        } else {elements[fiie].style.border='1px solid #7F9DB9';}
    }

  }
  return false;
}

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

来源:Mozilla.org

My

switch(e.type) {
    case gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED: blah
    case gapi.drive.realtime.ErrorType.CLIENT_ERROR: blah
    case gapi.drive.realtime.ErrorType.NOT_FOUND: blah
}

归结起来就是

with(gapi.drive.realtime.ErrorType) {switch(e.type) {
    case TOKEN_REFRESH_REQUIRED: blah
    case CLIENT_ERROR: blah
    case NOT_FOUND: blah
}}

你能相信这么低质量的代码吗?不,我们看到它是完全不可读的。这个例子无可否认地证明了没有必要使用with-statement,如果我的可读性是正确的;)