JavaScript采用词法作用域,即在函数创建的时候保存函数的静态作用域链。闭包是指函数变量可以被隐藏于作用域之内。
JavaScript函数在创建时便会创建并保存函数的[[Scope]]属性,[[Scope]]保存所有父级变量对象。因此所有的函数都是闭包。
闭包实例
function outFun() {
var v1 = "test";
function innerFun() {
alert(v1);
}
return innerFun;
}
var myFun = outFun();
myFun();//"test"
其中内部函数innerFun() 的[[Scope]]为:
innerFun.[[Scope]] = [
outFunContext.AO:{v1 : "test"},
globalContext.VO
]
因此在全局上下文中调用赋值给myFun的内部函数innerFun时,变量v1从innerFun.[[Scope]]中查找得到“test”。
同一父级函数的对象都引用一个[[Scope]]
在同一个父上下文中的多个函数有相同的[[Scope]]属性。在同一上下文中创建的函数,[[Scope]]属性中保存的是父类变量对象。因此同一个上下文中创建的多个函数的[[Scope]]属性相同。
var firstClosure;
var secondClosure;
function foo() {
var x = 1;
firstClosure = function () { return ++x; };
secondClosure = function () { return --x; };
x = 2; // 影响 AO["x"], 在2个闭包公有的[[Scope]]中
alert(firstClosure()); // 3
}
foo();
alert(firstClosure()); // 4
alert(secondClosure()); // 3
循环中的闭包
最初遇到闭包和最常考验闭包的就是循环中创建闭包。
function fun() {
var funcs = [];
for(var i = 0; i < 10; i++) {
funcs[i] = function() {
return i;
}
}
return funcs;
}
var f1 = fun();
f1[0]();//10
f1[5]();//10
f1[9]();//10
f1[0]-f1[9]这几个函数具有相同的[[Scope]],而[[Scope]]中的变量i随着循环递增,最终值为10,因此这几个函数的返回值都为10。
解决方法
(1)通过立即执行函数创建一个新的闭包
function fun() {
var funcs = [];
for(var i = 0; i < 10; i++) {
funcs[i] = (function help(x){
return function() {
return x;
}
})(i);
}
return funcs;
}
var f1 = fun();
f1[0]();//0
f1[5]();//5
f1[9]();//9
函数help在传入参数i时立即执行,并且返回一个函数作为数组的值,函数help每次会创建新的包含x的变量对象,而x是由参数i传递来的。
因此函数的[[Scope]]情况如下:
f1[0].[[Scope]] = [
helpContext.AO:{x:0},
funContext.AO:{i:10},
globalContext.VO
]
f1[5].[[Scope]] = [
helpContext.AO:{x:5},
funContext.AO:{i:10},
globalContext.VO
]
f1[9].[[Scope]] = [
helpContext.AO:{x:9},
funContext.AO:{i:10},
globalContext.VO
]
end
闭包可以实现函数的封装,获取函数内部变量。