2 JavaScript的执行过程
假如我们有下面一段代码,它在JavaScript中是如何被执行的 呢?
var name = "why"
function foo(){
var name = "foo"
console.log(name)
}
var num1 = 20
var num2 = 30
var result = num1 + num2
console.log(result)
2.1 第一步:初始化全局对象
JS引擎会在执行代码之前,在堆内存中创建一个全局对象:Global Object(GO)
- 该对象所有的作用域(scope)都可以访问
- 里面会包含
Date
、Array
、String
、Number
、setTimeout
、setInterval
等等 其中还要一个window属性指向自己
创建GO的伪代码
var GlobalObject = { String: '类', Date: '类', window: GlobalObject, name: undefined, num1: undefined, num2: undefined, result: undefined, foo: 0xa00//会指向专门为该函数在内存开辟一个空间地址 }
- 开辟的函数空间存储的内容和结构如下:
2.2 第二步:执行调用栈
代码要想运行,都需要先添加到内存里面,内存里面划分为栈结构和堆结构
- JS引擎为了执行代码,内部有一个执行上下文(
Execution Context Stack
, ECS) 。那么现在它要执行谁呢?执行的是全局的代码块(可以理解为函数)。 - 为了全局代码也能够正常执行,需要创建 全局执行上下文(
Global Execution Context (GEC)
) GEC
会放入到ECS
中执行,里面包含两部分内容:
第一部分:在代码执行前,在
parser
转成AST的过程中,会将全局定义的变量、哈描述等加入到GlobalObject
中,但是并不会赋值这个过程也称之为变量的作用域提升(hoisting)
var name = "why" function foo(){ var name = "foo" console.log(name) } console.log(num1) //假如在这里加入这行代码,打印的num1值为undefined,因为已经在go里面声明 var num1 = 20 var num2 = 30 var result = num1 + num2 console.log(result)
- 全局执行上下文GEC中的VO(
variable Object
)就是GO(Global Object
)对象
- 第二部分:开始执行代码执行,对变量赋值,或者执行其他的函数
2.3 遇到函数如何执行
在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context
,简称FEC),并且压入到ECStack中。
FEC中包括三部分内容:
第一部分:在解析函数成 为AST树结构时,会创建一个Activation Object(AO);
- AO中包括形参、arguments、函数定义和指向函数对象、定义的变量
第二部分:作用域链,由VO(在函数中就是AO对象)和父级(parent scope)VO组成,查找时会一层层查找
- 当我们查找一个变量时,真实的查找路径是沿着作用域链来查找的
- 第三部分:this绑定的值 (this的绑定规则后续再深究)
2.4 FEC执行代码
var name = "why"
foo(123)
function foo(num) {
console.log(m)
var m = 10
var n = 2o
console.log("foo")
}
- 编译阶段:创建全局的GO,并在GO内部定义变量,变量值为
undefined
- 编译阶段:为foo函数开辟内存,并将GO内的foo指向该地址
- 执行阶段:执行
var name ="why"
,为name赋值 执行阶段:执行
foo()
- 为foo创建一个FEC并放入ECStask中
- 真正开始执行foo中的代码
- 当foo执行完后foo的FEC出栈
- 为foo创建一个FEC并放入ECStask中
2.5 变量环境和记录
前面的讲解都是基于早期ECMA的版本规范
在最新的ECMA的版本规范中,对一些词汇进行了修改
通过上面的变化我们可以知道在最 新的ECMA标准中,我们前面的变量对象VO已经有另一个称呼(变量环境,VE)了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。