变量作用域
简而言之,JS变量存在于两个作用域里,即全局作用域和函数内的局部作用域。全局作用域的变量是通过在容器(如浏览器的window)里的定义的,局部作用域是定义在函数内部。
作用域链
首先,函数可以使用全局作用域的变量。其次,函数内部可以定义嵌套的子函数,而嵌套的函数可以递归般的使用上一级函数作用域内的变量直至全局变量,这就构成了一个作用域链。
作用域链的打破
每个函数的绑定都生成并维持了一个闭包,通常情况下,一个函数被调用之后,这个闭包环境就会被回收。但是如果函数在调用的时候,返回了一些内部的函数,那么等于是建立了一个通道,将内部作用域暴露给外部,从而打破了作用域链。形成了一条区别于自内而外到作用域链的新连接。
var F = function(){
var b = 2;
return function(){
b += 1;
return b;
};
}
var inner = F();
window.console.log(inner());//3
window.console.log(inner());//4
var another = F();
window.console.log(another()); //3
window.console.log(inner());//5
上述代码,可以看出一下两点:
- F()返回的是一个函数,这个函数调用的结果是返回b的值。这个作用域链inner->function->b,因此inner=F()后,就产生了一个新的作用域,维持着b这个局部变量不被回收。两次调用,不同的返回值,说明b还在作用域内,没有被回收。
- 作用域链是相互独立的,inner和another维护着两个独立的变量作用域链。
闭包的本质
来看一个经典的例子:
function F(){
var arr = [],i;
for(i = 0; i < 3; i++){
arr[i] = function(){
return i;
}
}
return arr;
}
window.console.log(arr[0]()); //3
window.console.log(arr[1]()); //3
window.console.log(arr[2]()); //3
上述代码,arr里包含了三个函数,都是 return i。 但是这个i的值,为什么是3,而不是0,1,2呢?这进一步说明,闭包维护的是变量之间的引用关系,而不是这些变量在定义的时候的值。那么如何使arr如预期那样工作呢?其实目标就是让arr存的三个函数有独立的作用域链。如上一节分析的那样,每次函数的调用,会建立一个作用域链,因此只要使arr里的三个变量是三次函数调用,就可以建立三个独立的作用域链。那么如何使arr的元素从函数变成函数调用呢?尝试以下两个方法: 1.立即调用函数
function FF(){
var arr = [];
for(var i = 0; i < 3; i++){
arr[i] = (function(x){
return function(){
return x;
}
})(i);
}
return arr;
}
var arr = FF();
window.console.log(arr[0]());//0
window.console.log(arr[1]());//1
window.console.log(arr[2]());//2
- 内部函数
function FF(){
var arr = [];
function proxy(x){
return function(){
return x;
}
}
for(var i = 0; i < 3; i++){
arr[i] = proxy(i);
}
return arr;
}
var arr = FF();
window.console.log(arr[0]());
window.console.log(arr[1]());
window.console.log(arr[2]());
其实两个方法本质上是一个方法。