标签:
但凡读书,或者学一门技术,都要问自己以下几个问题。
我下面就试着从这几个方向来阐述闭包这个概念。
在了解闭包之前,我们需要了解几个概念。本文在这里只做简单介绍,如需要进一步了解,请参考文章末尾的链接。
变量和函数的可作用范围,分为局部作用域和全局作用域。Javascript不具有块级作用域,而具有函数作用域。
变量和函数有权访问的其他数据。
每个函数在执行的时候,会把它的执行环境推入一个栈中,在函数执行完毕后执行环境出栈并被销毁。保存在其中的所有函数和比变量定义随之销毁,控制权返回到之前的执行环境中。全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。
作用域链用于保证对执行环境有权访问的变量和函数的有序访问。
闭包这个概念,在函数式编程里很常见,简单的说,就是使内部函数可以访问定义在外部函数中的变量。严格一点的定义是
在函数内声明另一个函数,并且返回这个函数。这个返回的函数和它的执行环境整体叫做闭包。
让我们来看一个例子:
function f1(){ var val = 10; } console.log(val); //Uncaught ReferenceError: val is not defined(…)
由于从函数外部无法访问函数内部的变量,所以报出了错误。那么如何能够访问到局部作用域的变量呢?
function f1(){ var val = 10; function f2(){ console.log(val); } return closure; } var f2 = f1(); f2(); // 10
在这段代码中,f2 函数和其执行环境构成了一整个闭包。对于常规的 f1() 方法, 在其内部的变量 val 应该在 f1() 方法执行完毕以后就被垃圾回收。但是 f1() 返回了一个新的方法 f2()。由于 f2() 访问了其外部函数的变量 val,val就构成了f2函数的执行环境。val 存在于f2的作用域链中,只要f2()方法没有被销毁,其作用域链中的变量和函数就不会被销毁, val 也就会一直存在。
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, 5); }
上面这个代码块会打印五个 5
出来,而我们预想的结果是打印 1 2 3 4 5。
之所以会这样,是因为 setTimeout 中的 i 是对外层 i 的引用。当 setTimeout 的代码被解释的时候,运行时只是记录了 i 的引用,而不是值。而当 setTimeout 被触发时,五个 setTimeout 中的 i 同时被取值,由于它们都指向了外层的同一个 i,而那个 i 的值在迭代完成时为 5,所以打印了五次 5
。
为了得到我们预想的结果,我们可以把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。
for (var i = 0; i < 5; i++) { (function (idx) { setTimeout(function () { console.log(idx); }, 5); })(i); }
假如我们要实现一系列的函数:add10,add20。我们为此构造了一个名为 adder 的构造器,如下:
var adder = function (x) { var base = x; return function (n) { return n + base; }; }; var add10 = adder(10); console.log(add10(5)); var add20 = adder(20); console.log(add20(5));
每次调用 adder 时,adder 都会返回一个函数给我们。我们传给 adder 的值,会保存在一个名为 base 的变量中。由于返回的函数在其中引用了 base 的值,于是 base 的引用计数被 +1。当返回函数不被垃圾回收时,则 base 也会一直存在。
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
参考:
学习Javascript闭包(Closure)
node-lessons/lesson11 at master · alsotang/node-lessons · GitHub
JavaScript作用域链
标签:
原文地址:http://www.cnblogs.com/timl525/p/5106221.html