执行环境与作用域

执行环境

执行环境(execution context, 又称执行上下文) 是JavaScript中最重要的概念, 它定义了变量或函数有权访问的其它数据, 决定它们各自的行为

每个执行环境都有一个与之关联的 变量对象(variable object), 执行环境中定义的所有变量和函数都保存在这个变量对象中

当代码开始执行时, 会进入其当前的执行环境, 在JavaScript中执行环境的类型有两种:

  • 全局环境: 最外围的一个执行环境, 在Web浏览器中指的window对象

  • 局部(函数)环境: 函数被调用执行时, 进入当前函数环境执行代码

函数调用栈

当一个函数执行时, 函数执行环境进入 函数调用栈(call stack, 也可以叫做环境栈), 函数执行完成后, 调用栈将其执行环境弹出, 把控制权交回给之前的执行环境. 因此, 在函数调用栈中, 栈底是全局环境, 栈顶是当前执行环境.

下面用红宝书里的例子和图示演示这个过程:

1
2
3
4
5
6
7
8
9
10
11
var color = 'blue';

function changeColor() {
if (color === 'blue') {
color = 'red';
} else {
color = 'blue';
}
}

changeColor();

execution-context-stack

执行环境详解

从上面可以知道, 当一个函数开始执行时, 将会创建一个新的执行环境. 在JavaScript解释器中, 调用一个执行环境会有两个阶段:

  1. 创建阶段(当一个函数被调用, 代码执行之前):

    • 创建作用域链
    • 创建变量对象:
      • 建立arguments对象, 检查参数上下文, 初始化属性和值并且创建一个副本+ 检查当前执行环境的函数声明, 在当前变量对象中创建一个属性, 并指向函数引用, 若属性存在, 则修改函数引用指向
      • 检查当前执行环境的变量声明, 并在当前变量对象中创建属性, 初始化为undefined, 若属性名存在, 则跳过
    • 确定this的指向

用代码的形式表现的话如下:

1
2
3
4
5
executionContextObject = {
scopeChain: {/* 作用域链: 包括当前所在环境的变量对象variableObject和包含其外部执行环境的变量对象*/},
variableObject: {/* 变量对象: 包括arguments, 函数, 变量*/},
this: {}
}
  1. 代码执行阶段

    赋值, 函数引用和执行代码

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function func(i) {
console.log(arguments[0]); // 20

console.log(typeof foo); // function
console.log(bar); // undefined

var foo = 'hello',
bar = function() {
return 'world';
};

console.log(foo); // hello

function foo() {
return 'hello';
}
}

func(20);

我们从代码示例中, console.log(typeof foo) 输出的是function, 而不是undefined, 是因为函数声明会优先于变量声明, 在检查var foo = 'hello'的时候, 会发现foo属性已经在变量对象中存在并跳过.

上面的代码在解释器中可以简单地认为是下面这种形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function func(i) {
function foo() {
return 'hello';
}

var bar;

console.log(arguments[0]); // 20

console.log(typeof foo); // function
console.log(bar); // undefined

foo = 'hello';

bar = function() {
return 'world';
};

console.log(foo); // hello
}