闭包(closure)
MDN中的解释
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
从技术角度讲,每个JavaScript函数都是闭包,因为他们都是对象且都关联到了作用域链。
举个例子
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
其中f2函数作为f1的内部函数,f2函数可以读取f1函数的局部变量,则称f2为一个闭包
理解
-
变量作用域
JavaScript变量作用域有两种,一种是全局变量,一种是局部变量。
JavaScript语言在函数内部就可以读取到全局变量。var a = 'hello'; function f1(){ console.log(a); } f1(); // hello
而在函数外部就不能读取函数内部的局部变量。
function f1(){ var a = 'hello'; } console.log(a); // undefined
-
链式作用域-如何从函数外部读取函数内部变量
正常情况下,函数外部无法读取到函数内部的变量,而定义在函数内部的函数,可以读取到函数的局部变量,看之前的例子:
function f1(){ var n=999; function f2(){ alert(n); // 999 } }
这是JavaScript特有的链式作用域的结构,即子对象会一级一级的向上寻找所有父对象的变量,所以父对象的对象对子对象都是可见的,反之不成立。
再看一个例子:
var n = 1000; function f1(){ var n = 999; function f2(){ alert(n); } return f2; } f1()(); // 输出结果是什么?
上面这个例子中,我们将内部函数f2作为返回值,在外部进行调用执行,想一下函数f1在执行的时候会创建作用域链,而函数f2是定义在f1的作用域链中的,所以函数f2是被绑定在f1的作用域链上的,无论函数f2在哪里执行这种绑定依旧存在。所以在执行函数f2是,会先在f1的作用域链中查找变量,所以上面例子输出结果应为999。
-
闭包概念
上例中f2函数即为一个闭包,可以简单理解闭包就是一个可以读取函数内部变量的函数。由于JavaScript中只有函数内部的子函数可以读取函数的局部变量,也可以理解是定义在函数内部的函数。
闭包的用途
-
读取函数内部的变量
-
让变量值不被回收,一直在内存中
function fn(){ var n = 1000; console.log(n++); } fn(); // 1000 fn(); // 1000
n作为内部变量,在第一次调用fn之后,即被清除掉不在内存中维护,再次调用fn又是初始化新的变量n,所以导致n的值没有发生变化
function fn(){ var n = 1000; function f1(){ console.log(n++); } return f1; } var f1 = fn(); f1(); // 1000 f1(); // 1001
f1函数作为闭包,调用了fn的局部变量n,而f1被赋值给了全局变量,导致f1始终在内存中,而f1依赖于fn,所以fn也会始终在内存中,不会因为调用了f1之后就会被回收掉。
注意事项
- 由于使用闭包不会回收掉父函数对象,增大内存消耗,可能会导致内存泄露或性能问题,需要在退出函数之前删除不适用的局部变量。
- 闭包可以在函数外部修改父函数内部的局部变量的值,在使用过程中尽量避免。
应用场景
-
封装变量
function counter(){ var privateCounter = 0; //私有变量 function change(val){ privateCounter += val; } return { increment:function(){ //三个闭包共享一个词法环境 change(1); }, decrement:function(){ change(-1); }, value:function(){ return privateCounter; } }; }; var counterA = counter(); var counterB = counter(); console.log(counterA.value());// 0 counterA.increment(); console.log(counterB.value());// 0 互不影响 counterB.increment(); console.log(counterA.value());// 1 console.log(counterB.value());// 1