js的运行环境有三种:
1. 全局环境 2. 函数内环境 3. eval环境
1. EC
Execute Context: 执行上下文。
1)全局执行上下文
js引擎遇到可执行的js代码,默认创建一个全局执行上下文。
2)函数执行上下文
js引擎遇到函数调用,会立即创建一个函数执行上下文。
执行上下文周期:
执行上下文周期分为两个阶段:
- 创建阶段
创建阶段的任务有三个:
1. 生成变量对象(VO)
2. 建立作用域链Scope
3. 声明this指向
this在创建阶段只是声明,在执行阶段根据函数运行时所在的执行上下文确定this指向的上下文的变量对象VO。
- 执行阶段
当函数执行内部代码时,进入执行阶段。
2. VO / AO / GO
Variable Object: 变量对象,创建阶段的变量对象。
Active Object: 激活后的变量对象,即执行上下文执行阶段的VO。
Global Object: 全局对象,全局执行上下文对应的变量对象(AO/VO)。
每个执行上下文对象都有一个变量对象的属性,它用于存放执行上下文周期的创建阶段的形式参数、函数声明和变量声明。不能被用户直接访问。
在执行上下文周期的执行阶段,VO中对应的属性被赋值,变为AO,AO中内容是变量查找的内容。
变量对象的创建顺序如下:
- 1. 创建arguments对象,初始化形式参数
function a(x,y) { console.log(x); } a(1); // 1 /* aContext: { AO: { x:1 } } */
- 2.查找函数声明(非函数表达式)
遍历函数声明,将其函数名称作为AO的属性,它会覆盖同名的属性。
function a(x,y) { function x() { } console.log(x); } a(1); // function x(){} /* aContext: { AO: { x: pointer to function x } } */
- 3.查找变量声明
查找遍历声明内容,将其变量名作为AO的属性。
1. 如果AO中已经有同名的属性,则该变量声明忽略。
function a(x,y) { function x() { } var x; console.log(x); } a(1); // function x(){} /* aContext: { AO: { x: pointer to function x } } */
2. 如果有同名属性,但是如果查询阶段赋值,会覆盖前面同名属性和对应的值。
function a(x,y) { function x() { } var x=2; console.log(x); } a(1); // 2 /* aContext: { AO: { x: 2 } } */
3. 如果AO中已经后同名的形式参数,变量声明也会被忽略。
function a(x,y) { var x; console.log(x) } a(1); // 1 /* aContext: { AO: { x: 1 } } */
3. 作用域链Scope
function a() { function b() { function c() { } } }
a[[Scope]] = [// 父作用域 globalContext.VO ] b[[Scope]] = [ aContext.VO, globalContext.VO ] c[[Scope]] = [ bContext.VO, aContext.VO, globalContext.VO ]
而执行上下文的Scope属性,指向当前作用的完整的作用域链。即函数的[[Scope]]属性加上当前作用域的AO对象。
// 例如: cContext = { AO:{ arguments: { length: 0 }, }, Scope: [AO, ..c[[Scope]]] }
代码执行后,查找变量会根据执行上下文的作用域链开始查找,即从当前函数执行上下文的AO对象开始查找,查找不到则到上层的变量对象查找,依次查找,直到查找到全局执行上下文的变量对象为止。
4. ECS
Execute Context Stack: 执行上下文栈,也称为函数调用栈。
用于存储管理所有的执行上下文对象。
示例:
function foo(i) { if(i == 3) { return; } foo(i+1); console.log(i); } foo(0);
// 运行结果
2
1
0
js引擎遇到可执行代码,默认生成全局上下文。即位于栈底。
ECS = [globalContext];
遇到函数调用,将函数入栈。
ECS.push(foo0Context); ECS.push(foo1Context); ECS.push(foo2Context); ECS.push(foo3Context);
其栈结构如下:
|------------------| |foo(3);console(2);| |------------------| |foo(2);console(1);| |------------------| |foo(1);console(0);| |------------------|
|foo(0); |
|------------------|
|globalContext |
|------------------|
函数执行完成后,一一出栈。最后的全局上下文在浏览器关闭的时候才会销毁。
ECS.pop();