可执行代码
javascript的可执行代码(executable code)的类型
全局代码 函数代码 eval代码
执行上下文栈
JavaScript引擎创建了执行上下文栈(Execution context stack ,ECS) 来管理执行上下文。
对于执行上下文,都有三个重要属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
-
变量对象
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
全局上下文
全局对象JavaScript代码中,作为JavaScript的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他对象所有预定义的对象 函数 属性。
在顶层JavaScript代码中,可以用关键字 this引用全局对象。
全局上下文中的变量对象就是全局对象函数上下文
在函数上下文中,我们用活动对象(activation object,AO) 来表示变量对象。活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。arguments属性值是Arguments对象。
执行过程
执行上下文的代码会分成两个阶段进行处理: 分析和执行
进入执行上下文
-
进入执行上下文
当进入执行上下文时,这个时候还没有执行代码。
变量对象会包括: 函数的所有形参(如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为undefined
- 函数声明
- 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性。
- 变量声明
全局上下文的变量对象初始化是全局对象。
- 函数上下文的变量对象初始化只包括Arguments对象
- 在进入执行上下文时会给变量对象添加形参 函数声明 变量声明 等初始化的属性值。
- 在代码执行阶段,会再次修改变量对象的属性值。
作用域链
当查找变量对象的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。函数创建
函数的作用域在函数定义的时候就决定了。词法作用域
函数有个内部属性[[scope]],当函数创建的时候,就会保存所有父变量对象到其中,可以理解为[[scope]]就是所有父变量对象的层级链,但是注意 [[scope]]并不代表完整的作用域链!。 ```
function foo() { function bar() { … } }
函数创建时,各自的[[scope]]为
foo.[[scope]] = [ globalContext.VO ];
bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
<a name="wH0as"></a>
### 函数激活
当函数激活时,进入函数上下文,创建VO/AO后,就会将活动对象添加到作用域的前端。
Scope = [AO].concat([[Scope]]);
<a name="OGeOx"></a>
## 捋一捋
var scope = “global scope”; function checkscope(){ var scope2 = ‘local scope’; return scope2; } checkscope();
1. checkscope()函数被创建,保存作用域链到内部属性[[scope]]
checkscope.[[scope]] = [ globalContext.VO ];
2. 执行checkscope函数,创建checkscope函数执行上下文,并把checkscope函数压入栈中
ECStack = [ checkscopeContext, globalContext ];
3. checkscope函数并不会立即执行,开始准备工作,复制函数 [[scope]]属性创建作用域
checkscopeContext = { Scope: checkscope.[[scope]], }
4. 第二步 用arguments创建活动对象,随后初始化活动对象, 加入形参 函数声明 变量声明
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: checkscope.[[scope]], }
5. 第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] }
6. 准备工作做完,开始执行函数,随着函数的执行,修改AO的属性值
checkscopeContext = { AO: { arguments: { length: 0 }, scope2: ‘local scope’ }, Scope: [AO, [[Scope]]] }
7. 查找到scope2的值,返回后函数执行完毕,函数上下文从执行上下文栈种弹出。
ECStack = [ globalContext ]; ```
js代码运行的各个阶段
在V8引擎中js代码的运行过程主要分成三个阶段
- 语法分析阶段。该阶段会对代码进行语法分析,检查是否有语法错误(SyntaxError),如果发现语法错误,会在控制台抛出异常并终止执行。
- 编译阶段 该阶段会进行执行上下文(Execution Context)的创建,包括创建变量对象、建立作用域链、确定 this 的指向等。每进入一个不同的运行环境时,V8 引擎都会创建一个新的执行上下文。
执行阶段 将编译阶段中创建的执行上下文压入调用栈,并成为正在运行的执行上下文,代码执行结束后,将其弹出调用栈。
执行上下文创建
js运行环境包括全局环境 函数环境 eval环境,其中全局环境和函数环境创建过程如下
第一次载入 JavaScript 代码时,首先会创建一个全局环境。全局环境位于最外层,直到应用程序退出后(例如关闭浏览器和网页)才会被销毁。
- 每个函数都有自己的运行环境,当函数被调用时,则会进入该函数的运行环境。当该环境中的代码被全部执行完毕后,该环境会被销毁。不同的函数运行环境不一样,即使是同一个函数,在被多次调用时也会创建多个不同的函数环境。
- 每进入一个不同的运行环境时,JavaScript 都会创建一个新的执行上下文,该过程包括:
- 建立作用域链(Scope Chain);
- 创建变量对象(Variable Object,简称 VO);
-
创建变量对象
变量对象,每个执行上下文都会有一个关联的变量对象,该对象上会保存这个上下文中定义的所有变量和函数。
而在浏览器中,全局环境的变量对象是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。相应的,在 Node 中全局环境的变量对象则是global对象。
在 JavaScript 中,词法环境又分为词法环境(Lexical Environment)和变量环境(Variable Environment)两种,其中: 变量环境用来记录var/function等变量声明;
- 词法环境是用来记录let/const/class等变量声明。
通过使用两个词法环境(而不是一个)分别记录不同的变量声明内容,JavaScript 实现了支持块级作用域的同时,不影响原有的变量声明和函数声明。
建立作用域链
作用域就是词法环境,而词法环境由两个成员组成。
- 环境记录(Environment Record):用于记录自身词法环境中的变量对象。
- 外部词法环境引用(Outer Lexical Environment):记录外层词法环境的引用。
通过外部词法环境的引用,作用域可以层层拓展,建立起从里到外延伸的一条作用域链。当某个变量无法在自身词法环境记录中找到时,可以根据外部词法环境引用向外层进行寻找,直到最外层的词法环境中外部词法环境引用为null,这便是作用域链的变量查询。
在执行阶段,变量对象(VO)会被激活为活动对象(AO),变量会进行赋值,此时活动对象才可被访问。在执行结束之后,作用域链和活动对象均被销毁,使用闭包可使活动对象依然被保留在内存中。这就是 JavaScript 代码的运行过程