从一道思考题说说闭包

这是一道从阮一峰老师的一篇博客《学习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 国际许可协议进行许可。

原文地址:https://www.cnblogs.com/fortunel/p/6380075.html