上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现。
本文就看看Execution Context中的scope chain。
做用域
开始介绍做用域链以前,先看看JavaScript中的做用域(scope)。在不少语言中(C++,C#,Java),做用域都是经过代码块(由{}包起来的代码)来决定的,可是,在JavaScript做用域是跟函数相关的,也能够说成是function-based。
例如,当for循环这个代码块结束后,依然能够访问变量”i”。
1
2
3
4
5
|
for(var i = 0; i < 3; i++){
console.log(i);
}
console.log(i); //3
|
对于做用域,又能够分为全局做用域(Global scope)和局部做用域(Local scpoe)。
全局做用域中的对象能够在代码的任何地方访问,通常来讲,下面状况的对象会在全局做用域中:
- 最外层函数和在最外层函数外面定义的变量
- 没有经过关键字”var”声明的变量
- 浏览器中,window对象的属性
局部做用域又被称为函数做用域(Function scope),全部的变量和函数只能在做用域内部使用。
1
2
3
4
5
6
7
8
9
|
var foo = 1;
window.bar = 2;
function baz(){
a = 3;
var b = 4;
}
// Global scope: foo, bar, baz, a
// Local scope: b
|
做用域链
经过前面一篇文章了解到,每个Execution Context中都有一个VO,用来存放变量,函数和参数等信息。
在JavaScript代码运行中,全部用到的变量都须要去当前AO/VO中查找,当找不到的时候,就会继续查找上层Execution Context中的AO/VO。这样一级级向上查找的过程,就是全部Execution Context中的AO/VO组成了一个做用域链。
因此说,做用域链与一个执行上下文相关,是内部上下文全部变量对象(包括父变量对象)的列表,用于变量查询。
1
|
Scope = VO/AO + All Parent VO/AOs
|
看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
console.log(x + y + z);
};
bar()
};
foo();
|
上面代码的输出结果为”60″,函数bar能够直接访问”z”,而后经过做用域链访问上层的”x”和”y”。
- 绿色箭头指向VO/AO
- 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)
再看一个比较典型的例子:
1
2
3
4
5
6
7
8
9
10
|
var data = [];
for(var i = 0 ; i < 3; i++){
data[i]=function() {
console.log(i);
}
}
data[0]();// 3
data[1]();// 3
data[2]();// 3
|
第一感受(错觉)这段代码会输出”0,1,2″。可是根据前面的介绍,变量”i”是存放在”Global VO”中的变量,循环结束后”i”的值就被设置为3,因此代码最后的三次函数调用访问的是相同的”Global VO”中已经被更新的”i”。
结合做用域链看闭包
在JavaScript中,闭包跟做用域链有紧密的关系。相信你们对下面的闭包例子必定很是熟悉,代码中经过闭包实现了一个简单的计数器。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function counter() {
var x = 0;
return {
increase: function increase() { return ++x; },
decrease: function decrease() { return --x; }
};
}
var ctor = counter();
console.log(ctor.increase());
console.log(ctor.decrease());
|
下面咱们就经过Execution Context和scope chain来看看在上面闭包代码执行中到底作了哪些事情。
1. 当代码进入Global Context后,会建立Global VO
.
- 绿色箭头指向VO/AO
- 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)
2. 当代码执行到”var cter = counter();”语句的时候,进入counter Execution Context;根据上一篇文章的介绍,这里会建立counter AO,并设置counter Execution Context的scope chain
3. 当counter函数执行的最后,并退出的时候,Global VO中的ctor就会被设置;这里须要注意的是,虽然counter Execution Context退出了执行上下文栈,可是由于ctor中的成员仍然引用counter AO(由于counter AO是increase和decrease函数的parent scope),因此counter AO依然在Scope中。
4. 当执行”ctor.increase()”代码的时候,代码将进入ctor.increase Execution Context,并为该执行上下文建立VO/AO,scope chain和设置this;这时,ctor.increase AO将指向counter AO。
- 绿色箭头指向VO/AO
- 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)
- 红色箭头指向this
- 黑色箭头指向parent VO/AO
相信看到这些,必定会对JavaScript闭包有了比较清晰的认识,也了解为何counter Execution Context退出了执行上下文栈,可是counter AO没有销毁,能够继续访问。
二维做用域链查找
经过上面了解到,做用域链(scope chain)的主要做用就是用来进行变量查找。可是,在JavaScript中还有原型链(prototype chain)的概念。
因为做用域链和原型链的相互做用,这样就造成了一个二维的查找。
对于这个二维查找能够总结为:当代码须要查找一个属性(property)或者描述符(identifier)的时候,首先会经过做用域链(scope chain)来查找相关的对象;一旦对象被找到,就会根据对象的原型链(prototype chain)来查找属性(property)。
下面经过一个例子来看看这个二维查找: