本文最后更新于 476 天前,其中的信息可能已经有所发展或是发生改变。
ES3版本
1.初始化全局对象
- JavaScript引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
- 该对象所有的作用域(scope)都可以访问,在浏览器中这个对象就是window
- 里面会包含Date、Array、String、Number、setTimeout、setlnterval等等
- 同时这里面还有一个window属性指向自己

2.创建执行上下文
- JavaScript引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。
- 全局的代码块为了执行会构建一个Global Execution Context(GEC)
- GEC会被放入到ECS中执行
- GEC被放入到ECS中里面包含两部分内容
- 在代码执行前,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值; 而function定义的变量会创建对应的函数对象(只有function定义的会),并在GO里面保存对应的内存地址,这个过程也叫作用域提升
- 在代码执行过程中,对变量赋值,或者执行其他的函数
3.关联VO对象
- 每一个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中
- 当全局代码被执行的时候,VO就是GO对象了

4.开始执行代码
- 在代码执行过程中,对变量赋值,或者执行其他的函数

5.遇到函数的调用时
执行前
- 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到ECS中
- 同时因为每个执行上下文都会关联一个VO
- 所以当进入一个函数执行上下文时,会创建一个AO对象(Activation Object)
- 同时会将变量被初始化为
undefined,这是因为在函数的执行上下文被创建时,它们尚未获得具体的实参值- 接受的或者定义的都会被初始化

执行中
- 这个AO对象会使用arguments作为初始化,并且初始值是传入的参数
- 比如
foo(A,B){ console.log(arguments)} foo(10,20) - 首先在函数内部,可以通过
arguments对象访问传入的所有参数10,20 - 调用的过程中 AO 对象中的
a和b被初始化为这个函数调用中传入的实参(即10和20)
- 比如
- 这个AO对象会作为执行上下文的VO来存放变量的初始化;
- 当一个函数被调用时,会创建一个 AO 对象,这个对象被用来存储该函数内的所有变量和参数

6.执行结束
- 当函数的代码执行完毕后,所有在函数内部定义的局部变量以及其他相关的执行信息(如
this的值等)都随之被销毁 - 销毁执行上下文并从调用栈中移除
- 一旦上下文被销毁,与之相关的所有局部变量(包括AO 中存储的变量)将不再可访问,其内存也将被释放或重新分配
- 如果没有对变量的引用,那么这个变量所占用的内存就会被标记为可以回收的区域。并可以由垃圾回收机制进行清理
作用域和作用域链
作用域
- 作用域它定义了变量和其他标识符的可访问范围
- 在ES5中全局是一个作用域,函数也会产生作用域
- 在ES6中,代码块,let,const等也都会产生属于自己的作用域
作用域链
- 作用域链用于解决变量和函数的查找问题,描述了在执行上下文中查找标识符的顺序和方式
- 当进入到一个执行上下文时,执行上下文也会关联一个作用域链
- 作用域链是一个对象列表,用于变量标识符的求值
- 当进入一个执行上下文时,作用域链被创建,并且根据代码类型,添加一系列的对象
- 通常作用域链在解析的时候就被确定了,作用域链与函数的定义位置有关,与函数的调用位置无关
具体查找流程
1.初始化全局对象并创建对应的 VO GO

2.代码开始执行并赋值

3.遇到函数调用,并创建对应的 VO AO

4.开始执行函数内的代码

5.当函数内的代码执行完成后
- 当foo函数执行完成会弹出执行上下文栈
- 把函数的返回值赋值给fn
- 同时因为inner对象里面有一个scope指向了AO1所以AO1的内存对象并不会被回收

6.在次遇到函数的调用,并创建对应的 VO AO

7.开始执行inner函数内的代码

8.开始查找变量
- 首先在自己的AO2里面找name,message,count
- 自己的AO2只找到name
- 往AO1里面查找message,count
- 找到所有变量并打印

9.函数执行完成后
- 当inner函数执行完成会弹出执行上下文栈
- inner AO2没有引用会被销毁

10.全局上下文执行完毕
- GO有一个window对象指向自身所以始终不会销毁

ES5之后
- 大体逻辑与ES3版本基本一致具体如下
- 执行上下文栈和执行上下文也是相同的
- 在ES5之后,执行一个代码,通常会关联对应的词法环境
- 一个词法环境是由环境记录(Environment Record)和一个外部词法环境(outerLexical Environment)组成
- 环境记录也分为两种
- 声明式环境记录
- 如函数声明、变量声明和等
- 对象式环境记录
- 对象环境记录用于定义ECMAScript元素的效果,例如WithStatement,
- 声明式环境记录
- 一个执行上下文里面又有两个环境
- 词法环境 LexicalEnvironment
- 用于处理let、const声明的标识符
- 在同一个变量环境里面有限查找词法环境在查找变量环境
- 变量环境 VariableEnvironment
- 用于处理var和function声明的标识符
- 词法环境 LexicalEnvironment
全局上下文执行的时候

遇到函数执行的时候

ES2023之后
- 明确指出了LexicalEnvironment 和 VariableEnvironment 这两个组件“始终是环境记录(Environment Records)”。
- 这意味着执行上下文中的这两个组件直接指向了环境记录,而不再通过其他结构(如词法环境)间接管理标识符的绑定
全局执行上下文
- 全局执行上下文关联的是一个全局环境记录(Global Environment Record):
- 全局环境记录实际上是一个包含了声明环境记录(Declarative Environment Record)
- 里面存放let 和count定义的东西
- 对象环境记录(Object Environment Record) 的组合体
- 里面存放window里面的东西还要var function定义的变量函数
- 全局环境记录的OuterEnv指向null
- 全局环境记录实际上是一个包含了声明环境记录(Declarative Environment Record)
- 变量查找过程
- 在查找一个变量名N 时
- 首先会在Declarative Environment Record (声明环境记录)中查找。
- 若Declarative Environment Record 中存在该绑定,则直接返回 true,即查找成功。
- 如果Declarative Environment Record(对象环境记录) 中没有找到该绑定,才会继续在Object Environment Record 中查找
函数执行上下文
- 函数环境记录(Function Environment Record)是声明式环境记录(Declarative Environment Record)
- 这意味着在函数的执行过程中,只会有一个环境记录,这个环境记录就是声明式环境记录。
- 一个声明式环境记录(Declarative Environment Record)是如何区分函数中存放的var变量、let/const这些的呢?
- CreateMutableBinding:用于创建 var 变量的可变绑定
- CreateImmutableBinding:用于创建 let 和 const 变量的不可变绑定
- 不可变绑定指的是一旦这个绑定(也就是这个名字到这个变量的关联)被创建后,它的绑定关系是不可改变的。
- 具体来说,一旦通过CreateImmutableBinding 创建了绑定,你不能用同样的名字再次创建另一个绑定,且在初始化之前,不能访问或修 改这个绑定
