闭包

一、闭包的概念:

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。

二、闭包的特性:

1.函数内再嵌套函数

2.内部函数可以引用外层的参数和变量

3.参数和变量不会被垃圾回收机制回收

搞不明白闭包的,十之八九都不会C语言,估计java也不会,否则不会这么纠结于这个概念。在java中,引用类型变量只要存在引用,就不会被销毁,引用本身也是个变量,是值传递,因此,可以通过函数返回,故此,任意一个局部对象,通过返回其引用的方式,可以在其它任何对象中使用,即便创建此对象的对象已经销毁,该对象由于还有活着的引用存在,而存在。js借鉴的java语法,与此类似。归根到底,系统创建的对象都存在与一个队列中,对每个对象都有引用计数,直到引用为0即销毁。A对象中创建了B对象,A中只保存B的引用变量,而不包含B本身。把这些搞明白了,闭包只是个概念,没什么值得深入研究的。

三、闭包的用途:

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

  function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){

      alert(n);

    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

四、使用闭包的注意点

1)内存泄露 :由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

五、闭包的使用场景

1.如上面举的例子, 闭包通常用来创建内部变量,使得这些变量不能被外部随意修改,同时又可以通过指定的函数接口来操作。

保护函数内的变量安全:如迭代器、生成器。在内存中维持变量:如果缓存数据、柯里化

2. 循环绑定事件(闭包允许内层函数引用父函数中的变量,但是该变量是最终值):

示例:

/**

* <body>

* <ul>

* <li>one</li>

* <li>two</li>

* <li>three</li>

* <li>one</li>

* </ul>

*/

var lists = document.getElementsByTagName('li');

for(var i = 0 , len = lists.length ; i < len ; i++){

lists[ i ].onmouseover = function(){

alert(i);

};

}

你会发现当鼠标移过每一个<li&rt;元素时,总是弹出4,而不是我们期待的元素下标。这是为什么呢?注意事项里已经讲了(最终值)。显然这种解释过于简单,当mouseover事件调用监听函数时,首先在匿名函数( function(){ alert(i); })内部查找是否定义了 i,结果是没有定义;因此它会向上查找,查找结果是已经定义了,并且i的值是4(循环后的i值);所以,最终每次弹出的都是4。

解决方法一:

var lists = document.getElementsByTagName('li');

for(var i = 0 , len = lists.length ; i < len ; i++){

(function(index){

lists[ index ].onmouseover = function(){

alert(index);

};

})(i);

}

解决方法二:

var lists = document.getElementsByTagName('li');

for(var i = 0, len = lists.length; i < len; i++){

lists[ i ].$$index = i; //通过在Dom元素上绑定$$index属性记录下标

lists[ i ].onmouseover = function(){

alert(this.$$index);

};

}

解决方法三:

function eventListener(list, index){

    list.onmouseover = function(){

        alert(index);

    };

}

var lists = document.getElementsByTagName('li');

for(var i = 0 , len = lists.length ; i < len ; i++){

    eventListener(lists[ i ] , i);

}

3. 匿名函数:

匿名函数最大的用途是创建闭包(这是JavaScript语言的特性之一),并且还可以构建命名空间,以减少全局变量的使用。

示例三:

var oEvent = {};

(function(){

var addEvent = function(){ /*代码的实现省略了*/ };

function removeEvent(){}

oEvent.addEvent = addEvent;

oEvent.removeEvent = removeEvent;

})();

在这段代码中函数addEvent和removeEvent都是局部变量,但我们可以通过全局变量oEvent使用它,这就大大减少了全局变量的使用,增强了网页的安全性。 我们要想使用此段代码:oEvent.addEvent(document.getElementById('box') , 'click' , function(){});

番外思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一。

  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()());


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭