一.什么是闭包(Closure)
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
….说的这是啥?
简单来说,闭包就是 : 闭包就是能够读取其他函数内部变量的函数
二.闭包什么时候产生
当你 定义 某个函数的时候,其实这个函数就已经产生了一个闭包.
当这个函数被执行的时候,他们的闭包能够让他们访问他们作用域内的数据。
三.闭包与函数的作用域之间的区别
- 产生的时间不同:
闭包是当你定义一个函数时创建的,而作用域是函数被执行的的时候创建的; - 消失的时间不同:
函数执行完毕,它的作用域就会被清除,下次函数被执行的时候是一个全新的作用域.
而闭包即使函数被执行完毕也不会消失.
// scope: global
var a = 1;
void function one() {
// scope: one
// closure: [one, global]
var b = 2;
void function two() {
// scope: two
// closure: [two, one, global]
var c = 3;
void function three() {
// scope: three
// closure: [three, two, one, global]
var d = 4;
console.log(a + b + c + d); // prints 10
}();
}();
}();
在上面的简单例子中,我们定义并立即调用了三个函数,所以他们都创建了作用域和闭包。
函数one()的作用域就是它自己,它的闭包让我们有访问它和全局作用域的权利。
函数two()的作用域就是它自己,它的闭包让我们有访问它和函数one(),还有全局作用域的权利。
同样,函数three()的闭包给我们访问所有作用域的权力。这就是为什么我们可以在函数three()中访问所有变量的原因。
总之,记住一句话 : 当函数被创建的时候,它的闭包就已经产生,相应的它所能够访问的作用域也已经确定
var v = 1;
var f1 = function () {
console.log(v);
}
var f2 = function() {
var v = 2;
f1(); // Will this print 1 or 2?
};
f2();
如果你想说2,那么你将会感到惊讶,这段代码实际上会打印1。原因是作用域和闭包并不相同。console.log方法会使用当我们定义f1()时所创建的f1()闭包,这意味着f1()的闭包值允许我们访问f1()和全局的作用域。
我们执行f1()的地方的作用域并不会影响闭包。实际上,f1()的闭包并不会给我们访问函数f2()作用域的权力。如果你删除全局变量v,然后执行这段代码,你将会得到错误消息:
var f1 = function () {
console.log(v);
}
var f2 = function() {
var v = 2;
f1(); // ReferenceError: v is not defined
};
f2();
四.闭包的用途
闭包可以用在许多地方。它的最大用处有两个。
1.获取函数内部的变量
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
2.让变量的值始终保持在内存中。
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),这时一定要小心,不要随便改变父函数内部变量的值。