闭包浅析

来源:互联网 发布:seafile数据是否加密 编辑:程序博客网 时间:2024/06/07 12:05

维基百科上对闭包的解释:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
Peter J. Landin 在1964年将术语闭包定义为一种包含环境成分和控制成分的实体。

当function里嵌套function时,内部的function可以访问外部function里的变量。

function foo(x) {    var tmp = 3;    function bar(y) {        alert(x + y + (++tmp));    }    bar(10);}foo(2)

  不管执行多少次,都会alert 16,因为bar能访问foo的参数x,也能访问foo的变量tmp。
  但,这还不是闭包。当你return的是内部function时,就是一个闭包。内部function会close-over外部function的变量直到内部function结束。

function foo(x) {    var tmp = 3;    return function (y) {        alert(x + y + (++tmp));    }}var bar = foo(2); // bar 现在是一个闭包, 函数引用了外部变量var bar2=foo(2);bar(10); //弹出16bar2(10); //也弹出16,每个闭包互不影响

  上面的脚本最终也会alert 16,因为虽然bar不直接处于foo的内部作用域,但bar还是能访问x和tmp。
  但是,由于tmp仍存在于bar闭包的内部,所以它还是会自加1,而且你每次调用bar时它都会自加1.
  上面的x是一个字面值(值传递),和JS里其他的字面值一样,当调用foo时,实参x的值被复制了一份,复制的那一份作为了foo的参数x。

function count() {    var arr = [];    for (var i=1; i<=3; i++) {        arr.push(function () {            return i * i;        });    }    return arr;}var results = count();var f1 = results[0];var f2 = results[1];var f3 = results[2];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果都是16。原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {    var arr = [];    for (var i=1; i<=3; i++) {        arr.push((function (n) {            return function () {                return n * n;            }        })(i));    }    return arr;}var results = count();var f1 = results[0];var f2 = results[1];var f3 = results[2];f1(); // 1f2(); // 4f3(); // 9

  如果一个函数访问了它的外部变量,那么它就是一个闭包。
  一个典型的例子就是全局变量的使用。

闭包的作用
1. 有的函数只需要执行一次,其内部变量无需维护,那么我们可以使用闭包访问外部变量
2. 设置缓存,可以将需要缓存的数据设置成外部变量,闭包可使用
3. 封装私有变量,外部不能直接访问,只能通过闭包访问,如闭包实现的计数器

var add = (function () {    var counter = 0;    return function () {return counter += 1;}})();add();//执行匿名子函数add(); add();// 计数器为 3

闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
4. 模拟类的模板机制,让不同的实例拥有独立的成员及状态,互不影响,即上例中的bar和bar2(类似3)

闭包引起的内存泄漏
垃圾回收器标记变量采取两个策略:标记清除和引用计数,目前大部分浏览器实现的都是标记清除的策略,变量离开环境便视为可删除变量,不过IE9之前的BOM和DOM对象采用的是引用计数策略,会引起内存泄漏现象

var element=document.getElementById("id");var myObject=new Object();myObject.element=element;element.someobject=myObject;

这个例子在DOM元素element和原生js对象myObject之间创建了循环引用,即使将DOM从页面中移除,也不会被回收,最好是在不使用它们的时候手工断开原生对象与DOM元素之间的连接。

myObject.element=null;element.someObject=null;

下面再来看一个闭包的例子

function assignHandler(){    var element=document.getElementById("id");    element.onclick=function(){        alert(element.id);    }}

匿名函数保存了一个对assignHandler()的活动对象的引用,因此无法减少element的引用数,可以改写一下代码来解决

function assignHandler(){    var element=document.getElementById("id");    var id=element.id;    element.onclick=function(){        alert(id);    }    element=null;}

因为闭包会引用包含函数的整个活动对象,所以要把element变量设置为null。

原创粉丝点击