定义
**闭包(closure)**是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
闭包产生的原因?
闭包的产生原因是因为在函数内部定义的函数可以访问外部函数的变量和参数,即使外部函数已经执行完毕,内部函数仍然可以访问那些变量和参数。
1、返回函数
最常用的一种形式是函数作为返回值被返回:(返回一个函数,外界保持对里的引用)
var outer = function(){
var b = 'local';
var fn = function(){
return b;
}
return fn;
}
console.log(outer()());
2、函数赋值
一种变形的形式是将内部函数赋值给一个外部变量:(将 outer 函数作用域里的函数 fn 赋值给全局作用域的inner,outer 执行之后,inner保持了对 outer 作用域里的引用)
var inner;
var outer = function(){
var name = 'Smile';
var fn = function(){
return name;
};
inner = fn ;
};
outer();
console.log(inner());
3、函数参数
闭包可以通过函数参数传递函数的形式来实现:(将F里的N作为参数给外界的Inner,F执行之后,外界Inner保持了对F里的引用)
var inner = function(fn){
console.log(fn());
}
var outer = function(){
var name = 'Smile';
var fun = function(){
return name;
}
inner(fun);
}
outer();
4、IIFE(立即执行函数)
由前面的示例代码可知,函数F()都是在声明后立即被调用,因此可以使用IIFE来替代。但是,要注意的是,这里的Inner()只能使用函数声明语句的形式,而不能使用函数表达式。
function inner(fn){
console.log(fn());
}
(function(){
var name= 'Smile';
var fun = function(){
return name;
}
inner(fun);
})();
5、循环赋值
在闭包问题上,最常见的一个错误就是循环赋值的错误。
function foo(){
var arr = [];
for(var i = 0; i < 3; i++){
arr[i] = function(){
return i;
}
}
return arr;
}
var bar = foo();
console.log(bar[0]());//2
原因:真正执行函数是bar[0]()
,单独拿出每个数组中第一个函数执行,外层循环已经执行完;其次方法执行产生的私有作用域,用到变量i,私有作用域里不存在 i,会根据作用域查找机制向上查询,找到的是已经是循环完毕赋值为 3 的全局变量 i
正确的写法如下
function foo(){
var arr = [];
for(var i = 0; i < 2; i++){
arr[i] = (function fn(j){
return function test(){
return j;
}
})(i);
}
return arr;
}
var bar = foo();
console.log(bar[0]());//0
5、getter & setter
我们通过提供getter()和setter()函数来将要操作的变量保存在函数内部,防止其暴露在外部
var getValue,setValue;
(function(){
var num = 0;
getValue = function(){
return num;
}
setValue = function(value){
if(typeof value === 'number'){
num = value;
}
}
})();
console.log(getValue());//0
setValue(1);
console.log(getValue());//1
6、迭代器
我们经常使用闭包来实现一个累加器
var add = (function(){
var count = 0;
return function(){
return ++count;
}
})();
console.log(add())//1
console.log(add())//2
类似地,使用闭包可以很方便的实现一个迭代器
function setup(x){
var i = 0;
return function(){
return x[i++];
}
}
var next = setup(['a','b','c']);
console.log(next());//'a'
console.log(next());//'b'
console.log(next());//'c'
总结:
无论通过何种形式,只要将内部函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。