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;
with ({x: 20}) {
var x = 30, y = 30;
alert(x);
alert(y);
}
alert(x);
alert(y);
在第一步中,变量
x
和
y
被声明并成为作用域链中的第一个对象,全局对象的一部分。
在第二步中,通过使用
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;
with ({foo: "bar"}) {
fn = function () {
return foo;
};
}
fn();
当函数执行时,会创建一个新的词法作用域并将其添加到作用域链中。
基本上,所有函数参数的标识符(名称)、使用
var
声明的变量以及使用
function
语句声明的函数的标识符都被绑定为一个新对象的属性,该对象在函数本身执行之前(当控制进入这个新的
execution context)在幕后创建。
这个对象无法通过代码访问,被称为
变量对象,例如:
var x = 10, y = 10;
(function () {
var x, y;
x = 30;
y = 30;
alert(x);
alert(y);
})();
alert(x);
alert(y);
在第一步中,与我的第一个示例一样,声明了变量
x
和
y
,它们是作用域链中的第一个对象,全局对象。
在第二步中,创建了一个新的函数对象,并将父级作用域此时存储在该函数的[[Scope]]中,包含现在的
x
和
y
。
在第三步中,调用函数,开始
变量实例化过程,在作用域链中创建一个新对象,其中包含在该新函数内部声明的局部作用域
x
和
y
变量,此时作用域链如下所示:
父级作用域 变量对象
________ _______________
| x = 10 | <--------- | x = undefined |
| y = 10 | | y = undefined |
¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
然后在第四步中,进行
x
和
y
的赋值,但由于已创建了新的词法作用域,因此不会影响外部值。
父级作用域 变量对象
________ ________
| x = 10 | <--------- | x = 30 |
| y = 10 | | y = 30 |
¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯
最后,在第5步中,函数结束并将作用域链恢复到其原始状态。
________
| x = 10 |
| y = 10 |
¯¯¯¯¯¯¯¯
推荐讲座: