作用域链
JavaScript中存在大括号级的作用域,但他有函数作用域,在函数内定义的变量函数外是不可见的,如果该变量在if或者for语句中,那他在代码中是可见的;
var a=1;
function f(){
var b=1;
return a;
};
输出结果为
在这里a变量是全局作用域,而b变量的作用域在函数f()内,所以在f()内,a和b都是可见的,在f()外,a可见b不可见。
var global=1; function outer(){ var outer_local=2; function inner(){ var inner_local=3; return inner_local+outer_local+global; }; return inner(); };
作用域链演示
闭包
每个函数都可以认为是一个闭包,因为每个函数都在其所在作用域中维护了某种私有联系,大多数时候,该作用域在函数体执行完之后就自行销毁了;
可以这样说,如果一个函数会在其父级函数返回之后留住父级作用域链接的话,相关闭包就会创建起来,但其实每个函数本身就是一个闭包,因为每个函数至少都有访问全局作用域的权限,而全局作用域是不会被破坏的
闭包#1
var a='global variable';
var F=function(){
var b='local variable';
var N=function(){
var c='inner local';
return b;
};
return N;
};
全局中不能直接访问b变量
函数N有自己的私有空间,同时也可以访问F()的空间和全局空间,所以b对它来说是可见的,因为F()是可以在全局空间中被调用的(它是一个全局函数),所以我们可以将它的返回值赋给另一个全局变量,从而生成一个可以访问F()私有空间的新全局函数。
闭包#2
var inner;
var a='global variable';
var F=function(){
var b='local variable';
var N=function(){
return b;
};
inner=N;
};
输出结果
闭包#3
function F(param){
var N=function(){
return param;
};
param++;
return N;
};
输出
注意:当我们返回函数被调用时,param++已经执行过一次递增操作了,所以inner返回的是更新后的值。由此可以看出,函数所绑定的是作用域本身,而不是在函数定义时该作用域中的变量或变量当前的返回值
循环中的闭包
function F(){
var arr=[],i;
for(i=0;i<3;i++){
arr[i]=function(){
return i
};
};
return arr;
};
输出结果
按通常估计,它们应该会依照循环顺序分别输出0、1和2,结果并不是
这并不是我们想要的结果,我们这里创建了三个闭包,它们都指向了共同的局部变量i,但是闭包并不会记录它们的值,它们所拥有的是相关域在创建时的一个连接(引用)。在这个例子中,变量i恰巧存在于定义这三个函数域中。对这三个函数中的任何一个而言,当要去获取某个变量时,它会从其所在作用域诸暨寻找距离最近的 i 值。由于循环结束时,i 的值为3,所以这三个函数指向了这一共同值。
那么如何纠正的呢,换一种闭包形式:
function F(){
var arr=[],i;
for(i=0;i<3;i++){
arr[i]=(function(x){
return function(){
return x;
};
}(i));
};
return arr;
};
输出结果
在这里,我们不再直接创建一个返回 i 的函数了,而是将 i 传递给了另一个即时函数,在该函数中,i 就被赋值给了局部变量 x ,这样一来,每次迭代中的 x 都会拥有各自不同的值了
闭包的应用示例
首先是创建 getter 和 setter。假设现在有一个变量,它所表示的是某一类特定的值,或某特定区间的值。我们不想将该变量暴露给外部。因为那样的话,其他部分的代码就有直接修改它的可能,所以我们需要将它保护在相关函数的内部,然后提供两个额外的函数,一个用于获取变量值,另一个用于给变量赋值。并在函数中引入某种验证措施,以便在赋值之前给予一定的保护。
我们需要将 getter 和 setter 这两种函数放在一个共同的函数中,并在该函数中定义secret变量,这使得两个函数能共享同一个作用域
var getValue,setValue;
(function(){
var secret=0;
getValue=function(){
return secret;
};
setValue=function(v){
if(typeof v==='number'){
secret=v;
};
};
}());
输出结果:
用闭包制作next()迭代器
通常情况下我们都知道如何用循环来遍历一个简单的数组,但是有时候我们需要面对更为复杂的数据结构,通常会有着与数组截然不同的序列规则,这时候就需要将一些“谁是下一个“的复杂逻辑封装成易于使用的next()函数,然后我们只需要简单的调用next()就可以实现对于相关的遍历操作了。
如下例子中,一个接受数组输入的初始化函数,我们在其中定义了一个私有指针 i ,该指针会始终指向数组中的下一个元素。
function setup(x){
var i=0;
return function(){
return x[i++];
};
};
输出: