本文共 1090 字,读完只需 4 分钟
概述
JavaScript 是函数式编程语言,作用域也是以函数为单位,那么,这些函数代码块是怎么样的顺序进行的呢, JS 的可执行代码又分为 3 种,不同类型的代码有不一样的执行环境。本文就梳理有关 JS 执行上下文(execution context),也叫执行环境的知识。
活动的执行代码的上下文在语言底层逻辑上构成一个执行上下文栈(excution context stack),我们知道,栈有两个行为,压入栈和弹出栈,并且有后进先出
的特点, 最先进入的项会在栈底最后弹出。
不同执行环境的有其相应的变量对象(Variable Object),某个执行环境的所有可执行代码都执行完毕后,该环境中的变量对象和函数定义会被清除。
函数代码会在执行完后清除变量占用的内存,全局代码则会在关闭环境,比如关闭浏览器后清除。
一、代码类型
JS 中可执行的代码可分为三种类型:
- 全局代码
- 函数代码
- Eval 代码
全局代码
在 web 浏览器中,全局执行环境是 window 对象,所有的全局变量和函数都是作为 window 的属性和方法存在。
全局代码的执行上下文栈可以表示为:
ECStack = [
globalContext
]
函数代码
当执行函数代码时,函数代码上下文被压入到执行上下文栈中。函数代码的执行环境中,有自己的内部的定义的变量和声明。
function foo1() {
var name1 = "a";
console.log(name1)
}
function foo2(){
var name2 = "b";
console.log(name2)
}
foo1();
foo2();
上面的执行上下文可以表示为:
ECStack = [
globalContext
];
ECStack.push(<foo1> functionContext);
ECStack.pop();
ECStack.push(<foo2> functionContext);
ECStack.pop();
一个函数,可能有多个执行上下文,每个函数的调用都会产生新的上下文。
function foo() {
...
}
foo("a");
foo("b");
foo("c");
eval 代码
eval
关键字接受一个字符串作为参数,并将其作为书写文字上下文的代码。
function foo(str, a) {
eval( str ); // 声明一个新变量
console.log(a, b);
}
var b = 123;
foo("var b = 456 ", 123) // 123, 456
以上代码引用《你不知道的 JavaScript》的例子,eval 函数中的字符串,被当做可执行代码,最后在 foo 函数中声明了 b 变量,并覆盖了 foo 函数外部的 b 变量。
eval 函数和 JS 的词法作用域的行为,会造成变量环境和执行上下文的混乱,尽量别使用它。
二、执行上下文栈
前面其实已经提到很多关于执行上下文栈的内容,简而言之,JS 在遇到全局代码,函数代码,eval 代码时,会创建相应的执行上下文,不同的上下文,有其变量对象(variable object: VO)和作用域。
看这一段代码:
function func3() {
console.log('fun3')
}
function func2() {
func3();
}
function func1() {
func2();
}
func1();
以上代码引用自冴羽的github,代码顺序用执行上下文栈来表示就是:
ECStack.push(globalContext);
// 调用 func1(), 进入 func1 执行环境
ECStack.push(<func1> functionContext);
// func1中调用了 func2,进入 func2 的执行上下文
ECStack.push(<func2> functionContext);
// func2 调用 func3, 进入 func3 的执行上下文
ECStack.push(<func3> functionContext);
// func3 执行完毕,退出 func3 执行环境
ECStack.pop();
// func2 执行完毕,退出 func2 执行环境
ECStack.pop();
// func1 执行完毕,退出 func1 执行环境
ECStack.pop();
函数代码执行完毕后,执行上下文栈底只剩下全局代码上下文 globalContext, 直到关闭浏览器才会彻底清空执行上下文栈。
总结
代码在执行时会创建由不同作用域构成的作用域链
,作用域链
保证 JS 中的变量和函数都能够有序地访问和执行。
如果是函数代码,则将其 活动对象
作为变量对象,活动对象最开始时只包含一个对象,即 arguments
对象。
后面的文章会介绍变量对象(VO)和作用域链的具体内容,敬请期待吧。