这是一道从阮一峰老师的一篇博客《学习Javascript闭包(Closure)》看到的思考题。(PS:开始的时候,是从阮一峰老师的博客看到的这道思考题。后来看《JavaScript高级程序设计》(第3版)(下面简称《JS高程》)才发现,原来这就是书中的例子啊。^_^|||)
思考题原代码如下,请试着理解两段代码的运行结果:
代码片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
代码片段二
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
下面我们尝试分析一下解答这道思考题的思路。
对于代码片段一:
1.开始声明了一个变量name和一个对象object;
2.对象object的第二个属性值是一个函数,这个函数里面嵌套了另一个函数;
3.代码最后是一个alert(),参数值是对对象object的第二个属性值中嵌套的子函数进行调用的结果。从这地方开始有一点绕了,我们一步一步来:
1) object.getNameFunc是取object的第二个属性值,也就是函数 function(){ return function(){ return this.name; }} ;
2) object.getNameFunc()是对函数 function(){ return function(){ return this.name; }} 的调用,得到函数 function(){ return this.name; } ;
3) object.getNameFunc()()是对函数 function(){ return this.name; } 的调用,得到返回值 this.name 。
4.所以alert()运行结果就是弹出 this.name 的值。关键现在 this.name 值是什么。
对于代码片段二:
类似于上面,最后我们可以推出,alert()运行结果就是弹出 that.name 的值。现在 that.name 值是什么。
《JS高程》中对闭包的解释很简单:闭包是有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
很多人讲闭包还会讲一些附加的条件,比如这个函数还要访问外部变量等等,目的是方便读者了解闭包的特性。然而这可能令初学者更为困惑,到底什么是闭包。其实《JS高程》中的定义也不够直接,第一句是通过特征归纳出了闭包的定义,而凡是不能从事物本质给出定义的结论,都不能较好的帮助我们认识事物本身。我觉得就初学者来说,只要认准一种形式的闭包就可以了,即函数中嵌套了子函数,这个子函数就是一个闭包,这也是书中提到的常见形式。例子中, function(){ return this.name; } 就是闭包。
这道思考题看起来似乎更多的是在帮助我们理解this,但其实如果缺少对闭包特性的掌握,可能也不太容易搞清楚代码运行的结果。
第一个例子中, alert(this.name); 相当于在全局作用域中访问this,此时this指向window,就是相当于 alert(window.name); 。所以,结果就是“The Window”。
第二个例子中,that是在getNameFunc属性值中定义的函数里声明的,that被赋值为this。object.getNameFunc()表示getNameFunc属性值中定义的函数被作为object的方法调用,所以this指向object,that与this指向相同,所以that也指向object。而that在闭包的作用域链上,除非闭包被销毁,否则即使that通过return被返回,也依然指向object。所以,结果就是“My Object”。
我们来看另外一个例子,下面是一段HTML代码:
1 ……
2 <body>
3 <ul>
4 <li>red</li>
5 <li>green</li>
6 <li>blue</li>
7 <li>yellow</li>
8 <li>pink</li>
9 </ul>
10 </body>
11 ……
现在,我通过getElementByTagName()获取到所有的li标签,这是一个类似数组的集合。然后,我要在页面中通过单击各个li让控制台中打印出其对应的下标值。
如果不采用闭包,我们通常会这么做:
1 <script>
2 window.onload=function(){
3
4 var lis=document.getElementsByTagName('li');
5
6 for(var i=0;i<lis.length;i++){
7 lis[i].index=i;
8 lis[i].onclick=function(){
9 console.log(this.index);
10 };
11 }
12 }
13 </script>
有了闭包之后,我们可以这样:(代码中只给出for循环的代替部分,这种方法中只有这部分与上面是不一样的)
1 for(var i=0;i<lis.length;i++){
2 //下面是一个函数自执行的写法
3 (function(j){
4 lis[j].onclick=function(){
5 console.log(j);
6 };
7 })(i); // 这一行也可以这样写 "}(i));"
8 }
或者这样:(同样只给出for循环的代替部分)
1 for(var i=0;i<lis.length;i++){
2 lis[i].onclick=(function(j){
3 return function(){
4 console.log(j);
5 }
6 })(i);
7 }
最后,《JS高程》提到“由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。”
本作品采用知识共享署名 4.0 国际许可协议进行许可。