闭包小实验

阮一峰的博客上有两段代码

  片段1

var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());   //The window

  片段2

var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());  //My Object

  不是很理解为什么出来这样的结果,来做个实验。

------------------------------------------------

对于片段1,我们写一个等价的代码

 1        var name = "The Window";
 2       var object = {
 3         name : "My Object",
 4         getNameFunc : function(){
 5                function fn(){
 6              return this.name;
 7           }
 8           return fn;
 9         }
10       };
11       alert(object.getNameFunc()());

在8行加断点, 显示this指向object

执行到11行的时候,  

object.getNameFunc() === function fn(){ return this.name}

这是this指向window了。等于最终把fn拔出来了。看来this这个特殊的关键字并没有闭包的功能

------------------------------------------------------------

 

对于片段2,写另一个等价的代码

 1         var name = "The Window";
 2       var object = {
 3         name: "My Object",
 4         getNameFunc: function(){
 5                var that = this;
 6                function fn(){
 7             return that.name;
 8           };
 9           return fn;
10         }
11       };
12       alert(object.getNameFunc()());

在第5,7,9,12行加断点

1)先跳到12行(函数执行行),此时this指向window,that指向 underfined;

2)  接着跳到5行(object.getNameFunc()),此时环境是

function(){
         var that = this;
         function fn(){
      return that.name;
         };
        return fn;
  }

这个函数,所以this指向object,that也是(实际要到第9行才能看到这样的情况,因为跳到第5行断点时,还没处理这行代码);

3) 接着跳到第9行(return fn === object.getNameFunc()),that和this还是都指向object(还是在这个环境中);

4) 接着跳到7行,此时that指向object,this指向window。因为that在

function(){
         var that = this;
         function fn(){
       return that.name;
         };
        return fn;
  }

环境内,因为return fn的闭包,that被保存在内存中了。

--------------------------------------------------------------------

总之,this是一个特殊的字段,它在不同的函数执行环境下会有不同的指向,并且!!不同的执行环境不能光看在代码中的样子,要看函数执行时它在什么环境里!!!

 

 

下面处理面试题,给一串li绑定事件,点击弹出序号。

下面贴一段完美代码

 1 var ul = document.getElementsByTagName('ul')[0];
 2         var list = ul.getElementsByTagName('li');
 3         for (var i = 0; i < list.length; i++) {
 4             var li = list[i];
 5             li.onclick = (function(num) {
 6                 function fn() {
 7                     alert(num);
 8                 }
 9                 return fn;
10             })(i);
11         }

给第三行加断点,一行一行执行。发先顺序是3,4,5,9,10,5,3,3,4,5,9,10,5, 3,3...

*因为给onclick绑定了一个自执行函数,所以在for循环到onclick的时候会执行(function(){})()里面的内容,有进入fn里

---------------------------------------------------------------------- 

看下一个有点问题的代码,每次弹出来都是7

 ✘

 1 var ul = document.getElementsByTagName('ul')[0];
 2         var list = ul.getElementsByTagName('li');
 3         for (var i = 0; i < list.length; i++) {
 4             var li = list[i];
 5             li.onclick = function() {
 6                 var num = i;
 7                 function fn() {
 8                     alert(num);
 9                 }
10                 return fn();
11             };
12         }

给第三行加断点,一行行执行,顺序是3,4,5,3,3,4,5.....

*因为给onclick绑定的只是一个函数,所以到事件发生的时候才会执行,所以在for循环的时候不会跳进return fn那行

 

-------------------------------------------------------------------------

 

对两行*有点不太理解? 下面继续做实验

第一段代码相当于这样。for循环的时候就每次都执行了bn函数,所以可以把每次的 i 传入bn里,利用num这个参数用闭包保存住

var ul = document.getElementsByTagName('ul')[0];
        var list = ul.getElementsByTagName('li');
        for (var i = 0; i < list.length; i++) {
            var li = list[i];
            function bn(num) {
                function fn() {
                    alert(num);
                }
                return fn;
            }
            li.onclick = bn(i);
        }

 

 

第二段相当于这样,onclick只绑定了bn函数,并没有执行,所以当事件发生执行的之后,i早已变成7了。弹出的也当然是7

 ✘

var ul = document.getElementsByTagName('ul')[0];
          var list = ul.getElementsByTagName('li');
          for (var i = 0; i < list.length; i++) {
                var li = list[i];
                function bn() {
                    var num = i;
                    function fn() {
                        alert(num);
                    }
                    return fn();
                }
                li.onclick = bn;
         }

-----------------------------------------

 

将第二段小小地改动一下

var ul = document.getElementsByTagName('ul')[0];
          var list = ul.getElementsByTagName('li');
          for (var i = 0; i < list.length; i++) {
                var li = list[i];
                function bn() {
                    var num = i;
                    function fn() {
                        alert(num);
                    }
                    return fn;
                }
                li.onclick = bn();
         }

和上面那段代码只在倒数两行有差别,可这段代码却可以达到理想的效果,原因也是因为for循环的时候就执行了bn,每次num都用闭包保存住了当前的i值

-------------------------------------------

 

另一种思路,在绑定事件之前就创建闭包

var ul = document.getElementsByTagName('ul')[0];
        var list = ul.getElementsByTagName('li');
        var arr = [];
        for (var i = 0; i < list.length; i++) {
            var li = list[i];
            (function(num) {
                li.onclick = function () {
                    alert(num);
                };
                /*arr.push(li);*/
            })(i);
        }
        /*for(var i = 0; i < arr.length; i++){
            arr[i].onclick = null;
        }*/

在每次click前就创建了闭包(num就是闭包保存的值,且因为li绑定的function用到了,它会被闭包保存住)。

从这里可以看出,闭包不一定要有return,只要在匿名函数内就可以创建闭包。

 

----------------------------------------------

关于释放内存

把上段代码注释取消,就是释放闭包的一个方法。(虽然页面上一般不会这样用,绑定完再清空不是发神经吗~)

可ie6要是不释放的话,内存会增加地非常快,根本不能活。

ie6是引用计数,闭包引用了全局里的i。 如果引用了全局中很多变量,即使在外头清理了全局变量,实际上这些变量已经被引用了,根本没有在内存中被删除。

 

posted @ 2014-08-19 18:41  欧欧欧子  阅读(181)  评论(0)    收藏  举报