初学闭包时一直以为很简单。但伴随对一个问题深入学习后,才算真正理解了闭包,同时也发现连<<JavaScript高级程序设计>>中都些不准确的地方。
我不准备从头介绍闭包的概念,而是在下面列了几份参考资料。其中以【参考2】最为简洁,本文也是因文中的习题而引出进一步的探讨。
从[参考2]最后提出的习题开始(应该来自<<JavaScript高级 程序设计>> 7.2),期望下面的程序可以输出"My Object",并且预期在取得this.name值时的标识符解析(identifier resolution)顺序如下:
(示例1)
上面程序会在log位置输出"The Window", 而不是期望的"My Object"。
其实红色部分错了。this并不指向创建它的对象,而是指向执行它的对象。
所以在执行下面这句话时,this其实是全局的window:
object.getNameFunc()();
这句话等价于以下两句:
vartempFunc = object.getNameFunc();
tempFunc();
它的执行对象就是全局的window,并且tempFunc和window.tempFunc完全等价的。
其执行对象的变化示意如下:
getNameFunc的执行对象是object, 而返回的闭包函数的执行对象是window。
增加一个示例,可以更明确的了解到这一点:
var name =
"The Window";
var object = {
name :
"My Object",
getNameFunc:function() {
console.log(this.name); // The Window
}
};
window.onload = object.getNameFunc;
(示例2)
所以如果要达成期望的输出,必须改变this的指向。有两种做法:
1. 不用this,而使用参数从父函数传入所需的数据。(不用this,不是简单将this去除。默认还是会使用this访问的。)
2. 显示改变this 指向 (比如call或apply)。
第一种方法的示例:
var name ="The Window";
var object = {
name :"My Object",
getNameFunc:function() {
var that =this;
return function(){
return that.name;
};
}
};
console.log( window.object.getNameFunc()());
(示例3)
第二种方法的示例:
1. 将调用方法改为如下:
console.log( object.getNameFunc().call(object) );
2. 简化调用,改为如下:
var name ="The Window";
var object = {
name :
"My Object",
getNameFunc:function() {
return(function(){
returnthis.name;
}).call(this);
}
};
console.log( object.getNameFunc());
(示例4)
到底发生了什么?
<<JavaScript高级程序设计>>:
当
某个函数第一次被调用时,会创建一个执行环境(execution context,环境比上下文更直白)及相应的作用域链(scope
chain),并把作用域(scope)赋值给一个特殊的内部属性([[Scope]])。然后,使用this、arguments和其它命名参数的值来
初始化函数的活动对象(activation object)。但在作用域链中,外部函数(outer)的活动对象(activation
object)处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。(7.2, 3rd Edition)
执行环境包含了三个部分 [详细内容在【参考4】中]:
a. 词法环境
b. 变量环境
c. ThisBinding
(图片来自【参考4】)
如果这样,示例的理解就应该是正确!所以,显然不是这么单纯。再思考一下函数的创建时机。函数的声明与其实例化(instantiation)的时间是不同的,而是在使用时创建。实例化过程中发生了什么?
[参考5]中给的解释: 与外部函数声明对应的函数对象会在全局执行环境的变量实例化过程中被创建。因此,外部函数对象的 [[scope]] 属性中会包含一个只有全局对象的“单项目(one item)”作用域链。
这样就对了。下面就是name的查找顺序(和示例1的this.name不同)。
如下面的示例所示:
var name ="The Window";
var object = {
name :"My Object",
getNameFunc:function() {
var name="Inner Of getNameFunc";
return function(){
console.log(name); //Inner Of getNameFunc
return name;
};
}
};
console.log( window.object.getNameFunc()()); //Inner Of getNameFunc
(示例5)
References:
1. PPK 谈 JavaScript 的 this 关键字
http://www.cnblogs.com/georgewing/archive/2009/09/29/1576641.html
2. 学习Javascript闭包(Closure)
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
3. 闭包的秘密
http://www.gracecode.com/archives/2385/
4. JavaScript Closures Explained
http://lostechies.com/derekgreer/2012/02/17/javascript-closures-explained/
5. 理解JavaScript闭包
http://www.cn-cuckoo.com/main/wp-content/uploads/2007/08/JavaScriptClosures.html