1、概念
在计算机科学中,闭包(英语:
Closure
),又称词法闭包(Lexical Closure
)或函数闭包(function closures
),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。 所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
这段话是来及维基百科的解释,这段话中有两个核心,变量与函数。
这里的变量指的是局部变量,及函数中的变量。
function f1(){
var n=1;
function f2(){
console.log(n); // 1
}
}
复制代码
上面例子中,n
是函数f1
内部的变量,f2
是定义在函数f1
内部的函数,其实闭包就是函数f1
,f2
和局部变量n
三者的总和。
2、我们来谈谈为什么要用闭包?
var n = 1;
function f1(){
console.log(n);//1
}
复制代码
上面的代码中n
是在全局作用域中,函数内部可以直接读取外部的变量。
function f1(){
var n = 1;
}
console.log(n);//error
复制代码
函数外部无法读取内部的变量。
如果想要读取函数内部的变量,该怎么办?
这里就引申出了闭包,在函数内部,再定义一个函数,然后再返回该函数,子函数就可以访问父级函数内的变量,同时父级函数内部的变量,对外界也变成了可被访问状态。
function f1(){
var n=1;
return function (){
console.log(n); // 1
}
}
var result = f1();
result();
复制代码
3、闭包的用途
闭包的最大用处有两个
,一个是读取函数内部的变量,另外一个就是让这些变量始终保持在内存中(函数执行形成一个不销毁的作用域)
。
function f1(){
var n=1;
add = function(){n++};
function f2(){
console.log(n);
}
return f2;
}
var result = f1();
result();//1
add();
result();//2
复制代码
上面的例子,我们能看出,函数f1
中的变量n
一直在内存中,并没有在f1
调用后自动清除掉。
原因就在于f1
是f2
的父函数,而f2
被赋给了一个全局变量result
,这导致f2
始终在内存中,而f2
的存在依赖于f1
,因此f1
也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
在没有闭包的语言中,变量的生命周期只限于创建它的环境。函数调用结束后,通常会被垃圾回收机制回收。但在有闭包的语言中,只要有一个闭包引用了这个变量,它就会一直存在。
在函数运行时,一旦外部的函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。
一个常见的面试题:
for( var i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log( i );
}, 1000 * i)
}
复制代码
答案是:每秒钟输出一个5,一共输出5次。 如何做到每秒中依次输出:0,1,2,3,4呢? 我们这里只提闭包的实现的方法:
for( var i = 0; i < 5; i++ ) {
((j) => {
setTimeout(() => {
console.log( j );
}, 1000 * j)
})(i)
}
复制代码
setTimeout
就是自执行函数中返回的函数,这样内部就能记住每次循环所在的词法作用域和作用域链。
4、闭包使用时需要注意事项
-
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
-
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
结语
闭包就是由Closure
翻译而来。我觉得很多人不懂闭包,是因为从字面上很难理解"闭包"。在中文里以前是没有“闭包”这个词的,就算把这两个字拆分开来,也很难理解它的真实意思。闭包
完全就是一个失败的翻译。