在js中,执行上下文(Execution Context)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variable object),作用域链(scope chain),this指针(this value),它们影响着变量的解析,变量作用域和函数this的指向。
上下文栈(Execution Context Stack)
js执行的时候会维护一个上下文栈,每次开始执行一个函数之前,js都要创建一个上下文对象,并将其压入上下文栈中。因此,当前正在执行的函数的上下文(简称当前上下文)总是在栈顶,这个函数一执行完,上下文就会从栈中弹出。
代码开始运行时,栈顶会先放入一个全局上下文,全局上下文同样有变量对象,作用域链和this指针。
举个例子:
function fun2() {
var b = 222;
}
function fun1() {
var a = 111;
fun2();
}
fun1();
c = 333;
- 开始执行时:
栈顶:全局上下文 - 代码执行到
fun1();
时,创建fun1的上下文,压入栈。这时栈变成:
栈顶:fun1上下文 / 全局上下文 - 代码执行到
fun2();
时,创建fun2的上下文,压入栈。这时栈变成:
栈顶:fun2上下文 / fun1上下文 / 全局上下文 - fun2执行完毕,fun2上下文弹出:
栈顶:fun1上下文 / 全局上下文
(这时执行权回到fun1内,栈顶也恰好回到了fun1上下文。可见这种机制能保证正在执行的函数的上下文总是在栈顶) - fun1执行完毕,fun1上下文弹出,执行权回到全局区域:
栈顶:全局上下文 - 所有代码都执行完毕,全局上下文弹出,栈为空。
执行上下文.变量对象(Variable Object)
我们已经说过,每次执行(注意是执行而不是声明!)一个函数之前,执行引擎都会创建一个上下文对象。创建上下文对象的时候,就会创建它的一个重要属性:变量对象。
创建变量对象的过程是这样:
- 建立arguments对象:属性名是'0'、'1'、'2'.....,属性值就是实际传入的参数。此外arguments.length是实际参数的个数。
- 找到这个将要执行的函数内的所有函数声明,储存在变量对象中,属性名就是函数名,属性值就是函数的引用(所在的内存地址)。如果有多个同名的函数声明,后出现的函数覆盖前面的属性值。
- 找到这个将要执行的函数内的所有变量声明,储存在变量对象中,属性名就是变量名,属性值是undefined。
还有一个概念叫做活动对象(activation object),活动对象其实和变量对象是同一个东西在不同时期的两种叫法。函数还未开始执行(创建上下文的期间)时叫变量对象,函数开始执行以后就叫活动对象。
变量对象让js有变量提升的特性
原来,在js执行函数之前,会先扫描一遍代码,将变量、函数声明都放到变量对象中。当执行函数时如果遇到一个变量、函数名,就会到活动对象中去找,发现有对应的属性名,就可以从变量对象中取出它的属性值来使用,而不用等到那一句声明语句之后!
例子:
function fun1(var arg) {
// 创建变量对象:{arg:987, fun2:fun2的地址, a:undefinded}
console.log(a); // 打印undefinded,因为活动对象中有键值对:a:undefinded。
var a = 111; // 如果将这一语句删除,上一句会直接报错!
console.log(a); // 打印111,因为活动对象中有键值对:a:111
fun2(); // 打印in fun2! 因为活动对象中有键值对:fun2:某个内存地址
return; // 即使是在return之后的声明,也会被放入变量对象!
function fun2() {
console.log('in fun2!');
}
}
fun1(987);
// 输出为:
// undefined
// 111
// in fun2!
为什么js不是块级作用域
这通过变量对象就可以解释了。因为只要在同一个函数中声明,所有变量都会保存在同一个变量对象中!所以在代码块中声明变量和在代码块外声明变量当然没有区别!
还记得我们刚才说“代码开始运行时,栈顶会先放入一个全局上下文”吗?在浏览器中,全局上下文的变量对象就是全局对象(就是window对象)!这就可以解释以下代码:
// 执行函数之前发现a的声明,将其加入window对象,设为undefined
var a = 111; // 在活动对象(也就是window)中将a赋值为111
console.log(window.a); // 打印111
window.a = 111;
console.log(a); // 打印111,因为在活动对象(也就是window)中找到了a:111
经过测试,Node.js全局上下文的变量对象不是全局对象(global)。
var a = 111;
console.log(global.a); // 打印undefined