JS函数作用域链

本文分析JS的函数作用域链,并从此角度理解闭包

关于JS作用域,请阅读JavaScript的作用域和提升机制

函数作用域链

当某个函数第一次被调用时,会创建一个执行环境(execution context)以及相应作用域链(scope chain)。
然后使用this, arguments和函数中其它命名参数的值来初始化函数的活动对象(activation object)做变量对象使用并放在
作用域链的首位。而作用域链的终点则是全局执行环境的变量对象。

对于每一个执行环境而言,都有一个表示(自己作用域中)变量(引用)的对象,称为变量对象。全局环境中变量对象
始终存在,而其它函数的变量对象只在执行过程中以活动对象形式存在。

在函数执行过程中,就按作用域链的顺序查找变量。

1
2
3
4
5
6
7
8
9
10
11
function compare(value1, value2){
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
}
var result = compare(5, 10);

上述代码在调用compare(5, 10)时,compare函数执行环境与作用域链如下图所示:

js-function-scope-chain.png

作用域链本质其实是一个指向变量对象的指针列表。

实际上,在创建(声明)函数时,会预先创建一个包含全局变量对象的作用域链,保存在内部[[scope]]属性中。
在调用函数时,将[[scope]]中内容复制到函数执行环境作用域链中,并在作用域链前端添加当前函数活动对象。

一般来说当函数执行完毕后其活动对象就会被销毁,内存中仅保存全局变量对象。但是闭包情况则是例外。

函数作用域链与闭包

在一个函数内部定义的函数,在其创建(即外部函数执行)时,会将外部函数的活动对象添加到自己的作用域链[[scope]]中。
从而可以访问外部函数中定义的所有变量。
如果外部函数,这个内部函数被返回,因为内部函数作用域链仍然引用着外部函数活动对象,所以虽然外部函数在执行完毕后,
执行环境的作用域链会被销毁,但其活动对象仍然留在内存中。

这种携带包含它的函数的作用域的函数也就是闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function createComparisonFunction(propertyName){
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
};
}
//创建函数(闭包)
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({name: "Amy"}, {name: "Bob"});
//解除引用,以便释放内存
compareNames = null;

上面代码中compareNames引用的匿名函数执行环境作用域链如下图所示:

js-closure-scope-chain.png

注意事项

1. 闭包与变量

闭包引用的外部函数的整个变量对象(活动对象),(变量对象)其中保存着对函数内部所有变量的引用而不是变量的值,因而
其只能取得外部函数中变量在执行完后的最后一个值。

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

因为这一点,上述函数返回的函数数组中每一个调用返回的i值都为10.

2. this变量

每个函数被调用时,其活动对象都会自动取得两个特殊变量:this,arguments。因此内部函数在作用域链上
搜索这两个变量时永远也不可能直接访问到外部函数中的这两个变量。不过可以通过将this保存在一个内部函数能够访问的
变量里,让其访问到该对象。

1
2
3
4
5
6
7
8
9
10
11
12
var name = "Window"
var object = {
name: "Object",
getNameFunction: function(){
return function(){
return this.name;
}
}
}
alert(object.getNameFunction()()); //"Window"

3. 内存泄漏

IE9以前版本对JS对象和DOM对象使用不同垃圾回收例程。在这些IE版本中如果闭包作用域链保存这HTML元素,
则该元素无法被正确回收。

相关文章

理解JavaScript中的作用域和上下文