执行上下文与作用域链
主要参考资料:
- 《JavaScript 高级程序设计(第4版)》
- P87(112/931)
- P309(334/931)
执行上下文
执行上下文 ,简称 上下文 ,包含需要被执行的代码。
执行上下文会在其代码执行完毕后被销毁。
上下文栈
JavaScript 维护了一个 上下文栈 ,用于调度上下文的执行。
上下文栈是一个栈,遵守先进先出的规则。
当上下文需要被执行时,上下文会被推到上下文栈。当上下文执行完后,上下文会被弹出上下文栈,将执行控制权交还给上下文栈中的上下文。
执行上下文主要分为两种:
-
全局上下文,是最先被推到上下文栈的上下文。
在浏览器中全局上下文是对象 window 。
-
函数上下文,即局部上下文。
每个函数调用都有一个上下文。
当执行函数调用时,会为函数调用创建一个上下文。
变量对象
每个上下文都有一个 变量对象(Variable Object) ,即 作用域(Scope) ,用于存储在当前上下文中定义的变量和函数。
函数的活动对象
当执行函数调用时,会为函数调用创建一个 活动对象(Activation Object) ,存储了函数调用时创建的参数变量。
函数调用的活动对象(Activation Object)会被用作函数上下文的变量对象。
作用域链
当执行一个上下文时,会为上下文创建一个 作用域链(Scope Chain) ,用于实现跨上下文地访问变量和函数的操作。
作用域链是一个链表,遵守链表的规则。
作用域链包含了指向上下文栈中所有上下文的变量对象的指针。
即作用域链中的指针指向某个上下文的变量对象。
作用域链的顺序是上下文栈中上下文被执行的顺序。
即正在执行的上下文的变量对象位于作用域链的最前端。
通过作用域链,正在执行的上下文可以访问其它上下文的变量对象,从而获取在其它上下文中定义的变量和函数。
作用域链的顺序,决定了各级上下文中的代码在访问变量和函数时的顺序。
作用域链增强
当执行 try/catch 语句中的 catch 语句时,会临时创建一个变量对象,并将这个变量对象添加到作用域链的前端。这个变量对象包含了错误对象的声明。
标识符查找
当上下文被执行时,必须确定上下文中的标识符表示什么,即需要找到标识符的声明。
标识符查找,即在作用域链中搜索标识符的声明。
标识符查找从作用域链的最前端开始:
- 如果在当前的作用域中找到标识符的声明,则搜索完成。
- 如果在当前的作用域中没有找到标识符的声明,则继续沿作用域链逐级搜索,如果在某个作用域中找到标识符的声明,则搜索完成。
- 如果遍历作用域链后,没有找到标识符的声明,则说明标识符没有声明,此时通常会报错。
可以看出,访问局部变量比访问全局变量快。
不过,JavaScript 引擎在优化标识符查找上做了许多工作,将来这个差异可能就微不足道了。(但无论如何优化,始终是有差异)
更多细节
在执行定义函数时,会创建一个当前上下文的作用域链,并保存在内部特性 [[Scope]] 中。
当执行函数调用时,会创建函数上下文,复制内部特性 [[Scope]] 创建作用域链,接着创建活动对象并将其添加到作用域链的前端。