怎样理解

一个函数记住并访问所在的词法作用域,即使函数在词法作用域之外执行,也能够访问涵盖的函数的内部作用域,这就形成了闭包。

1
2
3
4
5
6
7
function foo(a){
return function bar(){
console.log(a);
}
}
var bar=foo(2);
bar(); //2

无论经过多少标识符引用调用(将函数类型的值传递出去),bar()始终能涵盖foo()的内部作用域,所以在foo()词法作用域外部,也能访问变量a。
在定时器中也是如此

1
2
3
4
5
6
function wait(msg){
setTimeout(function timer(){
console.log(msg);
},1000);
}
wait('hello');

在定时器等待的1000ms中,wait的内部作用域并没有消失,timer函数依旧有涵盖了wait()内部作用域的闭包。
无论什么时候引擎调用这个函数(此处是timer),它的词法作用域都是完整的。

循环

举个常用例子,一个div里有四个button,加上点击事件

1
2
3
4
5
for(var i=0;i<aBtn.length;i++){
aBtn[i].onclick=function(){
console.log(i);
}
}

众所周知,无论点哪个按钮,输出的都是4,它们都是被封闭在一个共享的全局作用域中,实际上只有一个i。
我们来修改一下形成闭包

1
2
3
4
5
6
7
for(var i=0;i<aBtn.length;i++){
(function(i){
aBtn[i].onclick=function(){
console.log(i);
}
})(i);
}

每次迭代一份function,形成新的作用域。可以将新的作用域封闭在迭代内部,每个迭代都会含有一个具有正确值的变量给我们访问。

模块

必要条件:
1.必须有外部的封闭函数,该函数至少被调用一次。
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中产生闭包,并可以访问或者修改私有的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var foo=(function(con){
// 修改公共API
function change(){
public.foo1=foo2;
}
function foo1(){
console.log(con);
}
function foo2(){
console.log('abc'+con)
}
var public={
foo1:foo1,
change:change //一个修改的接口
};
return public;
})("hello");
foo.foo1();
foo.change();
foo.foo1();

通过在模块实例内部保留对公共API对象的内部引用,可以从内部对模块进行修改,包括添加或删除方法和属性,以及修改它们的值。