在上一篇文章【深入理解 JavaScript 的词法环境】详细介绍了词法环境,它是在V8引擎词法分析阶段用来登记变量的,这样在引擎真正执行代码的时候,就知道去哪里拿变量的值。也提到过,每次执行回调函数的时候,会把方法以执行上下文(Execution Context)
的方式压入执行栈(Call Stack)
,执行完以后会被弹出执行栈。
比如有代码:
var a; function foo() { a = "hi, i am foo"; console.log(a); } function baz() { foo(); } baz();
整个代码执行过程如下:
图中的蓝色方块就是执行上下文(Execution Context)
,包在蓝色方块的灰色区域就是执行栈(Call Stack)
,整个执行栈遵循后进先出的原则:
baz()
的时候,把baz执行上下文
压入执行栈。baz
调用foo
,把foo执行上下文
压入执行栈顶。foo
调用console.log
,把console.log执行上下文
压入执行栈顶。console.log执行上下文
是当前正在运行的执行上下文,在console执行完以后,console.log执行上下文
被弹出执行栈。foo执行上下文
是当前正在运行的执行上下文,在foo执行完以后,foo执行上下文
被弹出执行栈。baz执行上下文
是当前正在运行的执行上下文,在baz执行完以后,baz执行上下文
被弹出执行栈。那么只有function代码可以放到执行栈中运行吗?具体执行栈里有什么,它是怎么工作的呢?
事实上不仅仅是function可以作为执行上下文在执行栈中运行,在JS里定义了四种可执行代码:
执行上下文(Execution Context)有三个组成部分:
JS引擎是按照可执行代码来执行代码的,每次执行步骤如下:
前面的代码在执行完1-4步以后,整个环境看起来是这样的:
每个function都会新创建一个词法环境,function的词法环境中的scope,就是词法环境中的outer,作用域链就是沿着outer往上一层的词法环境里找变量/方法。
执行第五步,会先给变量a
赋值,然后执行console.log(a)
:
执行第六步,foo
baz
执行完后被弹出执行栈,这两个function对象还在内存中,等待垃圾回收。
变量环境组件(VariableEnvironment)是用来登记var
function
变量声明,词法环境组件(LexicalEnvironment)是用来登记let
const
class
等变量声明。
在ES6之前都没有块级作用域,ES6之后我们可以用let
const
来声明块级作用域,有这两个词法环境是为了实现块级作用域的同时不影响var
变量声明和函数声明,具体如下:
let
const
会登记在newEnv里面,但是var
声明和函数声明还是登记在原来的VariableEnvironment里。块级代码内的函数声明会被当做var声明,会被提升至外部环境,块级代码运行前其值为初始值undefined。
在这篇文章里介绍了执行上下文,它由三部分组成:LexicalEnvironment、VariableEnvironment和ThisBinding,并详细介绍了LexicalEnvironment和VariableEnvironment,在文章【JS:深入理解JavaScript-this】会详细介绍this。
文章转自:https://limeii.github.io/2019/05/js-execution-context/ 作者:Li Mei
欢迎关注「前端达人」公众号