Leo

理解闭包

字数统计: 903阅读时长: 4 min
2015/04/01 Share

第一段代码

1
2
3
4
5
6
7
8
9
10
11
12
function a(){

var i = 0;
function b(){
alert(++i);
}
return b;
}

var c = a();

c();

这段代码有2个特点:

1.函数b嵌套在函数a内部
2.函数a返回函数b

这样在执行完var c = a()后,变量c实际上是指向了函数b , b中用到了变量i

再执行c()后就会弹出窗口显示i的值(第一次为1)

这段代码就创建了一个闭包。

因为函数a外的变量c引用了a内的函数b

当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个我们所说的‘闭包’

第二段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var outter = [];

function closeTest(){

var array = ['one','two','three','four'];
for (var i = 0; i < array.length; i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(){
console.log(i);
}
outter.push(x);
}
}

closeTest();

console.log(outter[0].invoke());

console.log(outter[1].invoke());

console.log(outter[2].invoke());

console.log(outter[3].invoke());

很多初学者可能会认为答案是这样的: 0 1 2 3

但,实际上是: 4 4 4 4

其实,在每次迭代的时候,这样的语句x.invoke = function(){ console.log(i)}并没有被执行

只是构建了一个函数体为console.log(i)的函数对象。

当 i = 4 时,迭代停止,外部函数返回,当再去调用outter[0].invoke()时,i的值依旧为4

因此outter数组中的每一个元素的invoke都返回i的值:4

如何解决这一问题呢?

我们可以声明一个匿名函数,并立即执行他

第三段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var outter = [];

function closeTest2(){

var array = ['one','two','three','four'];
for (var i = 0; i < array.length; i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(no){
return function(){
console.log(no);
}
}(i);
outter.push(x);
}
}

closeTest2();

console.log(outter[0].invoke());

console.log(outter[1].invoke());

console.log(outter[2].invoke());

console.log(outter[3].invoke());

在这个例子中,我们为x.invoke赋值的时候

先运行一个可以返回一个函数的函数

然后立即执行之,这样,x.invoke的每一次迭代其实相当于执行了这样的语句:

第四段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//x == 0

x.invoke = function(){console.log(0);}

//x == 1

x.invoke = function(){console.log(1);}

//x == 2

x.invoke = function(){console.log(2);}

//x == 3

x.invoke = function(){console.log(3);}

这样就可以得到正确的结果了

闭包允许你引用存在于外部函数中的变量

但是

闭包并不是使用该变量创建时的值,相反,他是使用该变量最后的值

第五段代码

一个例子,在person之外的地方无法访问其内部的变量,而通过闭包的形式来访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var person = function(){

//变量作用域为函数内部,外部无法访问
var name = "default";

return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();

console.log(person.name);//直接访问没结果为undefined

console.log(person.getName());// 结果: default

console.log(person.setName('leo'));

console.log(person.getName());// 结果: leo

闭包的另一个重要用途是实现面向对象的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function Person2(){

//变量作用域为函数内部,外部无法访问
var name = "default";

return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}

var leo = Person2();

console.log(leo.getName());//结果:default

leo.setName('leo')

console.log(leo.getName());//结果: leo

.

var john = Person2();

console.log(john.getName());//结果:default

john.setName('john')

console.log(john.getName());//结果: john

由此代码可知,leo和john都可以称为是Person2这个类的实例,因为这两个实例对name这个成员的访问是独立的,互不影响的