作用域链,闭包

执行环境及作用域

概念

执行环境定义了变量或函数有权访问其他数据,决定他们各自的行为。每个环境中都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在变量对象中,解析器会在处理数据时在后台使用它。

全局执行环境是最外层的执行环境,web浏览器中默认是window对象,因此所有的全局变量和对象都是作为window对象的属性创建的。环境中的代码执行完毕后,环境会被销毁,其中保存的变量和函数也会销毁。

每个函数都有自己的执行环境。当执行流进入一个函数是,函数的环境就会被推入一个环境栈,函数执行完成后再出栈。

代码在一个环境中执行的时候会创建一个作用域链,它保证了对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果环境是函数则将其活动对象作为变量对象。活动对象最开始只包含一个变量:arguments对象。作用域链下一个变量对象就是外部环境,再下一个就是外部的外部,一直到全局。

标识符解析就是沿着作用域链一级一级搜索标识符的过程,总是从前端开始,向后回溯。

例子

1
2
3
4
5
6
7
var pubvar = 1;
function pub () {
var pravar = 2;
return pubvar + pravar;
}

alert(pub(2)); //调用pub()函数

全局环境中定义了变量pubvar和函数pub(),这时后台会创建一个作用域链
这个作用域链会保存在pub的scpoe属性中

scopechain1

  • 调用pub()函数后,又会创建一个执行环境B,没错,执行环境如其名是在运行和执行代码的时候才存在的,所以我们运行浏览器的时候会创建全局的执行环境。这个时候根据pub()函数scope属性中的作用域链把pub()函数内的变量对象(也就是活动对象)放入新的B环境中,作用域链也得到更新,如下图:

scopechain2

  • 上图的虚线表示正在执行,全局变量对象此时处于作用域链的第二位,所以标号变成了1。你可能也注意到那个arguments对象,它是在函数被创建的时候就一直存在的,无需用户创建。arguments对象保存的是函数圆括号内定义的参数,准确来说保存的是参数的值,因这里我们没有设置参数,所以显示未定义。此时我们把属于B环境的变量对象(也就是pub()函数中的所有函数和变量)叫做活动对象。因此我们可以说变量对象包含了活动对象,活动对象就是作用域链上正在被执行和引用的变量对象。我们从活动对象的名称中也能看出 “执行、运行、激活” 等意味。你可以这样理解,整个代码的运行总有一个起始的对象吧,不管这个起始是变量还是函数,总要有一个称呼,虽然我们把执行环境中的变量和函数的总称叫做变量对象,但这不能反映代码的动态性,为了区别于普通的变量对象,我们创造了活动对象的概念。
1
2
3
4
5
6
7
8
9
10
11
12
13
var pubvar = 1;
function pub () {
var pravar = 2;
return pubvar + pravar;
}

var pubvar2 = 3;
function pub2 () {
var pravar = 2;
return pubvar2 + pravar;
}

alert(pub(2)); //调用pub()函数

全局作用域链和执行环境如下:

scopechain3

调用pub()函数,执行环境和作用域链如下:

scopechain4

有被调用的pub2()函数仍然只是闲着,甚至没有被pub()函数在内部引用。由于pub2()没有参与整个pub()函数的调用过程,所以pub2()中不存在活动对象,只有“处于静止状态”的变量对象,当然也没有创建执行环境。

闭包

概念

由于作用域链的原因,内部环境可以访问外部的变量,反之则不行,因此我们使用闭包,将内部环境返回出去,将内部函数返回后复制给一个全局变量,这样就可以实现外部对内部的访问。

例子

1
2
3
4
5
6
7
8
9
10

function returnfunc (propertyName) {
return function (obj) { //-----这行定义并返回了一个闭包,也被称之为一个匿名函数
return obj[propertyName]; //这里用方括号法访问属性,因为属性是变量(returnfunc()函数的参数)
};
}

var savefunc = returnfunc("name"); //调用returnfunc()
var result = savefunc({name:"Picasso"});//调用savefunc()
alert(result); //返回字符串“Picasso”

最开始的作用域链和执行环境:

Closure1

调用returnfunc()函数,马上会创建一个包含returnfunc()变量对象的行环境,作用域链开始变化,如下图

Closure2

图的白色虚线表示执行程序产生的效果,它可能表示的是返回一个结果、复制某种值、产生一个新物体、建立某种联系等。随后returnfunc()函数会返回它内部的匿名函数,当匿名函数被返回后,整个作用域链和执行环境又发生了变化:

Closure2

我们看到匿名函数(闭包)被添加到了最作用域链的最前端,returnfunc()的执行环境被销毁,但我们注意到returnfunc()函数的活动对象仍然在被引用(匿名函数仍在访问propertyName参数),因此returnfunc()函数的变量对象仍然在内存中,成为活动对象。这就是为什么匿名函数就能访问returnfunc()函数定义的所有变量和全局环境定义的变量,毕竟returnfunc()的活动对象仍然保持“激活”状态。

闭包带来的问题

闭包只能取到包含函数中任何变量的最后一个值,因为它保存的时整个变量对象。

1
2
3
4
5
6
7
8
9
10
function createFunctions() {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result
}
createFunctions()

这时返回函数数组,每个函数都返回10。因为调用完createFunctions后,i的值变为10,此时每个匿名函数所保存的createFunctions()的活动对象中i都为10。

1
2
3
4
5
6
7
8
9
10
11
12
function createFunctions() {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function (num) {
return function(){
return num
}
}(i);
}
return result
}
createFunctions()

通过返回一个匿名函数,用IIFE的形式传参

闭包的作用

##模仿块级作用域

1
2
3
(function () {
//会计作用域
})()

以上代码定义并立即调用了一个匿名函数。将函数声明包裹在一对圆括号中,表示它是一个函数表达式,紧跟一个括号会立即调用这个函数
这种写法叫做IIFE(立即执行函数)

##设计模式

私有变量

在函数中定义的变量就可以当作私有变量,如果在函数内部创建一个闭包,闭包就可以通过作用域链访问这些变量,通过这种特性可以创建访问私有变量的特权方法

构造函数中定义特权方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function MyObject() {
//私有变量和函数
var privateVarible = 10;

function privateFunction() {
return false;
}

//特权方法
this.publicMethod = function () {
privateVarible++;
return privateFunction();
}

}
var a = new MyObject();
a.publicMethod

私有作用域中定义私有变量或函数

通过在私有作用域中低音私有变量或函数,同样也可以创立特权方法,其基本模式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function () {
//私有变量和私有作用域
var privateVarible = 10;

function privateFunction() {
return false;
}
//构造函数 MyObject是一个
MyObject = function () {

}
MyObject.prototype.publicFunction = function () {
privateVarible++
return privateFunction();
};

})();

在本模式中,私有变量和函数是由实例共享的,由于特权方法在原型上定义,因此所有实例使用的是同一个函数

模块化

为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
var singleton = function(){
var privateVarible = 10;
function privateFunction() {
return false;
}
return {
publicProperty: true;
publicMethod : function () {
privateVarible++;
return privateFunction();
}
}
}

这个模块模式使用了一个返回对象的匿名函数。在匿名函数内部首先定义私有变量和函数,然后返回一个对象字面量作为函数的值
返回,其中只包含可以公开的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var application = function() {
//私有变量和函数
var components = new Array();
//初始化
components.push(new BaseComponent());

//公共

return {
getComponentCount: function(){
return components.length;
},
registerComponent:function(){
if(typeof components == "object"){
components.push(Component());
}
}
};
}();

##增强版模块化