JavaScript中执行环境, 执行栈, 作用域的介绍及实例.

执行环境和词法环境

执行环境(Execution Context):

执行环境包括全局执行环境和函数执行环境.

  1. 当JavaScript代码最初开始运行时,全局执行环境被创建.
  2. 当某个函数被调用时,函数执行环境被创建.

execution_context

词法环境(Lexical Environment):

  1. 描述变量名与特定变量或函数的关系
  2. 基于词法嵌套结构

词法环境一般来说和作用域(scope)同义,详见.

全局环境和全局对象

当JavaScript代码开始刚开始运行时,全局执行环境就会被创建.在JavaScript中全局指代码或变量不包含在某个function对象中.
在全局执行环境中,JavaScript引擎会创建全局对象和一个特殊的变量this,全局执行环境下两者的值相同.
在一般的执行环境中,还会有outer environment,全局环境是处于最外层的outer environment.
在全局环境中定义变量和函数会自动将变量和函数与全局对象关联. 以浏览器为例:
a === window.a
func() === window.func()

函数调用和执行栈

函数每被调用一次,都会创建一个执行环境置于执行栈顶部,调用自己本身也会创建.

1
2
3
4
5
6
7
function b(){

};
function a(){
b();
};
a();

这段代码执行过程:

  1. 编译器编译源代码;
  2. 全局执行环境被创建, b和a保存于内存空间;
  3. 代码一行行执行;
  4. 执行到a();时,新的执行环境被创建;
  5. 新的执行环境在执行栈中全局环境上方,执行栈顶部的执行环境处于运行状态;
  6. 函数a中调用b(),此时在执行栈顶部的执行环境又变成b;
  7. 当b执行结束时,跳出执行栈,然后是a,然后是全局.

stack

作用域

变量名相同,在不同的作用域中,就是不同的变量,在内存空间中占用的是不同的位置.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function b(){
var myVar;
console.log(myVar);
}

function a(){
var myVar = 2;
console.log(myVar);
b();
}

var myVar = 1;

console.log(myVar);
a();
console.log(myVar);
// 1
// 2
// undefined
// 1

scope

变量声明提前 Hoisting

1
2
3
4
5
6
7
8
9
10
b();
console.log(a);

var a = 'Hello';

function b(){
console.log('called b')
}
// 'called b';
// undefined

在大多数编程语言中,会出现错误,因为代码一般是一行一行执行的,但是由于JavaScript中存在变量声明提前,所以出现上述结果.
而a的结果是undefined的原因是var a = 'Hello';中提前的只有var = a;,赋值的部分并未被提前.

相当于:

1
2
3
4
5
6
7
8
var a;

function b(){
console.log('called b')
}

b();
console.log(a);

上述代码只是为了便于理解,实际上并不是真正以这样的方式执行.

实际原因是执行环境的创建存在两个阶段:

1. 创建阶段

在这个阶段,JS引擎为变量和函数设置内存空间,在代码开始一行一行执行时,就可以获取内存空间中存在的变量或函数.其中函数整体保存于内存空间,而变量只有声明存在,所赋的值不会保存于内存中,因此在这个阶段,所有的变量值都为undefined.在JavaScript中,所有变量的初始值都为undefined.

2. 执行阶段

在这个阶段,代码开始一行一行执行.

作用域链

1
2
3
4
5
6
7
8
9
10
function b(){
console.log(myVar);
}
function a(){
var myVar = 2;
b();
}
var myVar = 1;
a();
//1

每个执行环境都可以获取其外部环境中存在的变量,b的外部环境是全局环境,因此可以获取到全局环境中声明的myVar.这里的外部环境是词法意义上(lexical environment)的.

scope_chain

在不同的作用域调用同一个函数,变量的值也可能会有差异.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function a(){

function b(){
console.log(myVar);
}

var myVar = 2;
b();
}
var myVar = 1;

a();
//2

此时函数b的外部词法环境是a,因此会从a中获取myVar的值.

作用域与ES6中的let

在执行环境的创建阶段,变量依然会被初始化为undefined被保存在内存空间,但只有在执行阶段执行到那一行代码时,变量才可以被使用.

1
2
3
4
console.log(c);

let c = 28;
// Uncaught ReferenceError: c is not defined
1
2
3
4
console.log(c);

var c = 28;
// undefined

用了let,js引擎就有了块级作用域.

1
2
3
4
5
if (true){
let c = 2;
}
console.log(c)
// Uncaught ReferenceError: c is not defined
1
2
3
4
5
if (true){
var c = 2;
}
console.log(c)
//2

参考