如果Javascript中的“with”语句创建了一个新的作用域,为什么这个闭包每次都不包含新作用域中的新“x”?

3

如果Javascript中的with语句创建了一个新的作用域,那么点击链接不应该显示不同作用域中的不同x吗?但实际上并没有。

<a href="#" id="link1">ha link 1</a>
<a href="#" id="link2">ha link 2</a>
<a href="#" id="link3">ha link 3</a>
<a href="#" id="link4">ha link 4</a>
<a href="#" id="link5">ha link 5</a>


<script type="text/javascript">

    for (i = 1; i <= 5; i++) {

        with({foo:"bar"}) {
            var x = i;
            document.getElementById('link' + i).onclick = function() { alert(x); return false; }
        }

    }

</script>

1
"with" 不是“好的部分”之一,有其原因。 - Sky Sanders
1个回答

27

with语句并不会创建一个完整的新词法作用域,它只是在作用域链前面引入了一个对象,例如,如果你捕获了i变量,它将起作用

for (var i = 1; i <= 5; i++) {
  with({x:i}) {
    document.getElementById('link' + i).onclick = function() {
      alert(x);
      return false;
    };
  }
}

让我用另一个例子更好地解释一下:

var x = 10, y = 10;   // Step 1

with ({x: 20}) {      // Step 2

  var x = 30, y = 30; // Step 3

  alert(x); // 30
  alert(y); // 30
}

alert(x); // 10
alert(y); // 30

在第一步中,变量xy被声明并成为作用域链中的第一个对象,全局对象的一部分。
在第二步中,通过使用with语句引入了一个新的对象({x:20})到作用域链中,现在作用域链看起来像这样:
   ________              ________
  | x = 10 | <--------- | x = 20 |
  | y = 10 |             ¯¯¯¯¯¯¯¯¯
   ¯¯¯¯¯¯¯¯ 
在第三步中,执行了另一个var语句,但它没有任何效果,因为只有函数创建一个完整的词法作用域。 var语句没有效果,但赋值有,所以当解析x变量时,它被解析到作用域链上的第一个对象,即我们使用with引入的对象。 y标识符也被解析,但在链中的第一个对象中找不到它,所以查找继续向上,并在最后一个对象上找到它,赋值后的作用域链如下所示:
   ________              ________
  | x = 10 | <--------- | x = 30 |
  | y = 30 |             ¯¯¯¯¯¯¯¯¯
   ¯¯¯¯¯¯¯¯ 
with语句结束时,作用域链最终恢复为:
   ________ 
  | x = 10 |
  | y = 30 |
   ¯¯¯¯¯¯¯¯ 

编辑: 让我稍微扩展一下并谈论一下函数。

创建一个函数时,它的当前父级作用域会被绑定,例如:

var fn;
// augment scope chain
with ({foo: "bar"}) {
  fn = function () { // create function
    return foo;
  };
}​​
// restored scope chain
fn(); // "bar", foo is still accessible inside fn

当函数执行时,会创建一个新的词法作用域并将其添加到作用域链中。
基本上,所有函数参数的标识符(名称)、使用var声明的变量以及使用function语句声明的函数的标识符都被绑定为一个新对象的属性,该对象在函数本身执行之前(当控制进入这个新的execution context)在幕后创建。
这个对象无法通过代码访问,被称为变量对象,例如:
var x = 10, y = 10;   // Step 1

(function () {        // Step 2
  var x, y; 

  x = 30;             // Step 4
  y = 30; 

  alert(x); // 30
  alert(y); // 30
})();                 // Step 3

alert(x); // 10       // Step 5
alert(y); // 10

在第一步中,与我的第一个示例一样,声明了变量xy,它们是作用域链中的第一个对象,全局对象。
在第二步中,创建了一个新的函数对象,并将父级作用域此时存储在该函数的[[Scope]]中,包含现在的xy
在第三步中,调用函数,开始变量实例化过程,在作用域链中创建一个新对象,其中包含在该新函数内部声明的局部作用域xy变量,此时作用域链如下所示:
  父级作用域           变量对象
   ________              _______________
  | x = 10 | <--------- | x = undefined |
  | y = 10 |            | y = undefined | 
   ¯¯¯¯¯¯¯¯              ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
然后在第四步中,进行xy的赋值,但由于已创建了新的词法作用域,因此不会影响外部值。
  父级作用域           变量对象
   ________              ________
  | x = 10 | <--------- | x = 30 |
  | y = 10 |            | y = 30 | 
   ¯¯¯¯¯¯¯¯              ¯¯¯¯¯¯¯¯ 

最后,在第5步中,函数结束并将作用域链恢复到其原始状态。

   ________ 
  | x = 10 |
  | y = 10 |
   ¯¯¯¯¯¯¯¯ 

推荐讲座:


所以闭包将包含这个“对象”作为最顶层的范围?这是一种混合范围链吗...还是只是一个普通的范围链?因此,如果我们在这些嵌套的“with”中调用匿名函数,那么我们可以有一个范围链,看起来像“scope,scope,scope,scope,object,scope,object,scope,object,scope”?(最后一个范围是最顶层的范围) - nonopolarity
@Jian Lin:我已经扩展了我的答案,讨论了函数以及如何创建新的词法作用域。 - Christian C. Salvadó

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