JavaScript的闭包案例---2

目录

前言

  接续之前的一篇JavaScript的闭包案例—1,加深对《JavaScript权威指南》P187的理解。


描述

  使用闭包,可以实现共享私有状态,实现私有存取器属性。本质上就是在同一个作用域链上定义了两个或多个闭包,来共享同样的私有变量。这是十分重要的一种应用,但是要十分小心因为这种做法将那些本不希望共享的变量共享给了其他闭包,这会造成严重的错误并让我们不解。
  假设有这样的需求:需要一个函数对象数组(数组每一项是一个函数),对于某个确定的数组元素,调用之可以返回数组下标。
  “看似正确”的做法如下:

1
2
3
4
5
6
7
var funcs = [];

for(var i=0;i<10;i++){
funcs[i] = function(){return i;};
}

console.log(funcs[5]()); //10

  正如注释所写,很奇怪的,将打印输出10而并不是5。原因就在于:在循环中,定义了10个函数(对应有自己的作用域链),也就形成了10个闭包,这些闭包都是在同一个函数调用中定义的,因此共享作用域,共享变量i。for循环结束的条件是i>=10。也就是说,当代码执行完成跳出for循环时,i的值是10。而这10个闭包的return i;所使用的正是这个共享的变量i,也就是10。因此,funcs[0]();~funcs[9]();执行的结果都是10。

  对此,引用书上一句话进行总结:

关联到闭包的作用域链都是活动的,记住这一点非常重要。嵌套的函数不会将作用域内的私有成员复制一份,也不会对所绑定的变量生成静态快照(static snapshot)—而是共享了作用域变量。

对于上述需求,可采用以下两种方式解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Created by gaochang on 2016/5/30.
*/


var funcs = [];

for(var i=0;i<10;i++){
funcs[i] = (function(v){
return function (){
return v;
}
}(i));
}

console.log(funcs[5]()); //5

以上方法用到了JavaScript中的“立即执行函数”的概念。注意这里funcs[i] = (function(v){这行的function左边的(括号是非常重要的。如果不写这个左圆括号,JavaScript解释器会尝试将关键字function解析为函数声明语句。只有用了左圆括号,才会正确将其解析为函数定义表达式,这在区分概念上很重要(注:试了一下,其实不写圆括号,结果也是对的,,但是尽管有时候没有必要也不应省略,这是习惯用法。)而一旦正确解析为函数定义表达式,也就相当于创建了新的闭包。匿名函数内的v外界不可访问,是独立的作用域,此时写为立即执行函数(传入i并执行),返回值就是正确无误的i,而不会是上述的10。

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Created by gaochang on 2016/5/30.
*/


function constfunc(v){
return function (){
return v;
}
}

var funcs = [];

for(var i=0;i<10;i++){
funcs[i] = constfunc(i);
}

console.log(funcs[5]());

这种写法中,将对数组每个元素进行赋值的函数抽离出来,结构清晰的同时,也实现了函数作用域的转化(不在for循环内),也就是闭包范围的变化。这样,每次调用函数时,就创建了一个新的作用域链和新的私有变量,互相不会影响。