1. js的作用域链
JavaScript中不存在大括号级的作用域,但它有函数作用域,也就是说,在某个函数内定义的变量在函数外是不可见的。
在下面的例子中,我们在函数 F 中定义了另一个函数 N ,那么在 N 中可以访问的变量即来自它自身的作用域,也可以来自“父级”作用域,这就形成了一条作用域链(scope chain),该链的长度(或深度)取决于我们的需要。
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner local";
};
};
2.利用闭包突破作用域链
上述例子中,变量 a 和 函数 F 位于全局作用域(G)中,每个函数也都会拥有一块属于自己的私有空间(例如 F) ,用于存储一些别的变量(例如 b)以及内部函数(例如 N)。如图所示
在上图中,如果我们在 a 点,就位于全局空间中。而如果在 b 点,就在函数 F 的空间里,即可以访问F 的空间,也可以访问全局空间,如果在c 点,就位于函数N 中,可以访问全局空间、F空间 和 N 空间。其中 a 和 b 之间是不连通的,但如果愿意的话我们可以将c 点和 b 点连通起来的,或者说将 N 与 b 连通起来。当我们将N的空间扩展到 F 以外,并止步于全局空间以内时,就产生了一件有趣的东西 – 闭包。
接下来N将会和 a 一样置身于全局空间,而且由于函数还记得它再被定义时所设定的环境,因此它依然可以访问F 空间并使用 b。这时就有一个有趣的现象:N 和 a 同处于一个空间,但 N 可以访问 b ,而 a 不能。
那 N 是如何突破作用域链的呢?我们只需要将它们升级为全局变量(不使用 var)或者通过 F 传递(或返回)给全局即可
- 闭包 #1
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner local";
**return b;**
};
**return N;**
};
函数F中包含了局部变量 b,因此后者在全局空间里是不可见的。但 b 对于N来说是可见的。因为 F() 是可以在全局空间中被调用的,所以我们可以将她的返回值赋值给另一个全局变量,从而生成一个可以访问 F() 私有空间的新全局函数。
> var inner = F();
> inner();
**"local variable"**
- 闭包 #2
var inner; //placeholder
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner local";
**return b;**
};
**inner = N;**
};
我们来看看 F() 被调用时会发生什么:
> F();
首先我们申明了一个全局函数占位符,然后将 N 赋值给了全局变量。由于 N() 是在 F() 内部定义的,它可以访问 F() 的作用域,即使该函数后来升级成了全局函数,它依然保留对 F() 作用域的访问权。
> inner();
**"local variable"**
- 相关定义与闭包
事实上,每个函数都可以被认为是一个闭包。因为每个函数都在其所在域中维护了某种私有联系,但在大多数时候,该作用域在函数体执行完后就自行销毁了-- 除非发生一些有趣的事(像上面所说),导致作用域被保持。
目前,可以这样说,如果一个函数会在其父级函数返回之后留住对父级作用域的链接的话,相关闭包就会被创建起来。
再来看一个闭包的例子,这次使用函数参数:
function F (param) {
var N = function () {
return param;
};
param ++;
return N;
}
然后调用:
> var inner = F(123);
> inner();
**124**
这里当我们的返回函数被调用时,param已经执行过一次递增操作了,所以 inner() 返回的是更新后的值,由此可以看出:函数所绑定的是作用域本身,而不是在函数定义时该作用域中的变量或变量当前所返回的值。
- 循环中的闭包
function F() {
var arr = [], i;
for (i = 0; i < 3; i ++) {
arr[i] = function () {
return i;
};
}
return arr;
}
这是一个三次的循环操作,它在每次迭代中都会创建一个返回当前循环序号的新函数,并添加到一个数组中返回。我们运行一下函数
> var arr = F();
> arr[0]();
**3**
> arr[1]();
**3**
> arr[2]();
**3**
这显然不是我们想要的结果,那为什么会这样呢,我们在这里创建了三个闭包,而它们都指向了一个共同的局部变量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 就会拥有各自不同的值了。也可以定义一个 “正常点的” 内部函数来实现这个功能。
function F() {
function binder(x) {
return function() {
return x;
};
}
var arr = [], i;
for (i = 0; i < 3; i ++) {
arr[i] = binder(i);
}
return arr;
}
3.getter 与 setter
接下来看一个闭包的应用示例,首先是创建 getter 和 setter。假设有一个变量,它表示某类特定值或某特定区间的值,我们不想将该变量暴露给外部,我们需要将它保护在相关函数内部,然后提供两个额外的函数 ——获取变量和修改变量。并在函数中引入某种验证措施。
var getValue,setValue;
(function(){
var secret = 0;
getValue = function(){
return secret;
};
setValue = function(v){
if (typeof v === "number") {
secret = v;
}
};
})();
这里一切都是通过一个即时函数来实现的,我们在其中定义了全局函数getValue() 和 setValue(),并以此来确保局部变量的不可直接访问性。
>getValue();
**0**
>setValue(123);
>getValue();
**123**
4.迭代器
该例是闭包在迭代器方面的功能。该例子是一个接受数组输入的初始化函数,我们在其中定义了一个私有指针 i,该指针会始终指向数组中的下一个元素。
function setup(x) {
var i = 0;
return function() {
return x[i++];
};
}
我们用一组数据调用一下setup(),就可以创建出next() 函数,而我们只需重复调用一个函数,就可以不停的获取下一个元素。
>var next = setup(['a','b','c'])
>next();
**"a"**
>next();
**"b"**
>next();
**"c"**
参考书籍:《JavaScript面向对象编程指南》
新人刚开始学习js,有不对的地方还请大家留言指正,不胜感谢!