function baz() {
var x = 1;
return {
foo: function foo() { return ++x; },
bar: function bar() { return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
上述代码可以用这张图来表示:
图 11. 共享的[[Scope]]
在某个循环中创建多个函数时,上图会引发一个困惑。如果在创建的函数中使用循环变量(如”k”),那么所有的函数都使用同样的循环变量,导致一些程序员经常会得不到预期值。现在清楚为什么会产生如此问题了——因为所有函数共享同一个[[Scope]],其中循环变量为最后一次复赋值。
复制代码 代码如下:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
有一些用以解决这类问题的技术。其中一种技巧是在作用域链中提供一个额外的对象,比如增加一个函数:
复制代码 代码如下:
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function (x) {
return function () {
alert(x);
};
})(k); // 将k当做参数传递进去
}
// 结果正确
data[0](); // 0
data[1](); // 1
data[2](); // 2
闭包理论的深入研究与具体实践可以在本系列教程第16章闭包(Closures)中找到。如果想得到关于作用域链的更多信息,可以参照本系列教程第14章作用域链(Scope chain)。
下一章节将会讨论一个执行上下文的最后一个属性——this指针的概念。
This指针
A this value is a special object which is related with the execution context.
Therefore, it may be named as a context object (i.e. an object in which context the execution context is activated).
this适合执行的上下文环境息息相关的一个特殊对象。因此,它也可以称为上下文对象[context object](激活执行上下文的上下文)。
复制代码
任何对象都可以作为上下文的this值。我想再次澄清对与ECMAScript中,与执行上下文相关的一些描述——特别是this的误解。通常,this 被错误地,描述为变量对象的属性。最近比如在这本书中就发现了(尽管书中提及this的那一章还不错)。 请牢记:
a this value is a property of the execution context, but not a property of the variable object.
this是执行上下文环境的一个属性,而不是某个变量对象的属性
复制代码
这个特点很重要,因为和变量不同,this是没有一个类似搜寻变量的过程。当你在代码中使用了this,这个 this的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻。this的值只取决中进入上下文时的情况。
顺便说一句,和ECMAScript不同,Python有一个self的参数,和this的情况差不多,但是可以在执行过程中被改变。在ECMAScript中,是不可以给this赋值的,因为,还是那句话,this不是变量。
在global context(全局上下文)中,this的值就是指全局这个对象,这就意味着,this值就是这个变量本身。
复制代码 代码如下:
var x = 10;
console.log(
x, // 10
this.x, // 10
window.x // 10
);
在函数上下文[function context]中,this会可能会根据每次的函数调用而成为不同的值.this会由每一次caller提供,caller是通过调用表达式[call expression]产生的(也就是这个函数如何被激活调用的)。例如,下面的例子中foo就是一个callee,在全局上下文中被激活。下面的例子就表明了不同的caller引起this的不同。
复制代码 代码如下:
// "foo"函数里的alert没有改变
// 但每次激活调用的时候this是不同的
function foo() {
alert(this);
}
// caller 激活 "foo"这个callee,
// 并且提供"this"给这个 callee
foo(); // 全局对象
foo.prototype.constructor(); // foo.prototype
var bar = {
baz: foo
};
bar.baz(); // bar
(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // 这是一个全局对象
(bar.baz, bar.baz)(); // 也是全局对象
(false || bar.baz)(); // 也是全局对象
var otherFoo = bar.baz;
otherFoo(); // 还是全局对象
如果要深入思考每一次函数调用中,this值的变化(更重要的是怎样变化),你可以阅读本系列教程第10章This。上文所提及的情况都会在此章内详细讨论。
总结(Conclusion)
在此我们完成了一个简短的概述。尽管看来不是那么简短,但是这些话题若要完整表述完毕,则需要一整本书。.我们没有提及两个重要话题:函数(functions) (以及不同类型的函数之间的不同,比如函数声明与函数表达式)与ECMAScript的 求值策略(evaluation strategy) 。这两个话题可以分别查阅本系列教程第15章函数(Functions) 与第19章求值策略(Evaluation strategy)。
如果你有任何评论,问题或者补充,我很欢迎在文章评论中讨论。
祝大家学习ECMAScript顺利。
您可能感兴趣的文章: