共计 5237 个字符,预计需要花费 14 分钟才能阅读完成。
javascript 代码解析过程
执行上下文和作用域是 javascript 中十分重要的局部,要弄清楚它们首先就要说到 javascript 的运行机制,javascript 代码被解析通过了以下几个步骤
- Parser 模块将 javascript 源码解析成形象语法树(AST)
- Ignition 模块将形象语法树编译成字节码(byteCode),再编译成机器码
- 当函数被执行屡次时,Ignition 会记录优化信息,由 Turbofan 间接将形象语法树编译成机器码
全局上下文
理解完以上 javascript 运行机制之后,咱们来看看以下全局代码的执行形式
console.log(user)
var user = 'alice'
var num = 10
console.log(num)
以上代码通过如下步骤才被执行
-
javascript –> ast
- 全局创立一个 GO(GlobalObject)对象,GO 中有很多内置模块,如 Math、String、以及 window 属性,其中 window 的值为 this,也就是本身 GO 对象
- 全局定义的变量 user 和 num 会增加 GO 对象中,并赋值为 undefined
-
ast –> Ignition
- V8 引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
- GEC 存在 VO(variable Object),在全局上下文中指向 GO 对象
-
Ignition –> 运行后果
- 通过 VO 找到 GO
- 将 user 赋值为 alice,将 num 赋值为 10
图示如下
以上代码的执行的后果为
undefined
10
- parser 模块将源代码编译为 AST 时,曾经将 user 和 num 定义到 VO 对象中,值为 undefined
- 打印 user 的时候,没有执行到 user 的赋值语句,所以 user 的值依然为 undefined
- 打印 num 的时候,曾经执行了给 num 赋值的语句,所以 num 的值为 10
函数上下文
定义函数的时候,执行形式和全局又有些不同
var name = 'alice'
foo(12)
function foo(num){console.log(m)
var m = 10
var n = 20
console.log("foo")
}
以上代码通过如下步骤才被执行
-
javascript –> ast
- 全局创立一个 GO(GlobalObject)对象,GO 中有很多内置模块,如 Math、String、以及 window 属性,其中 window 的值为 this,也就是本身 GO 对象
- 全局定义的变量 name 会增加 GO 对象中,并赋值为 undefined
- 函数 foo 会开拓一块内存空间,比方为 0x100,用来存储父级作用域(parent scope)和本身代码块,函数 foo 的父级作用域就是全局对象 GO
- 将 foo 增加到 GO 对象中,赋值为内存地址,如 0x100
-
ast –> Ignition
- V8 引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
- GEC 存在 VO(variable Object),在全局上下文中指向 GO 对象
-
Ignition –> 运行后果
(1)执行全局代码- 通过 VO 找到 GO
- 将 name 赋值为 alice
- 执行函数 foo 前,创立函数执行上下文(Function Excution Context),存在 VO 指向 AO 对象
- 创立 Activation Object,将 num、m 都定义为 undefined
(2) 执行函数
- 将 num 赋值为 12,m 赋值为 10,n 赋值为 20
- 函数 foo 执行实现,从调用栈(ECStack)栈顶弹出
图示如下
所以下面代码执行后果为
undefined
预编译
在 Parser 模块将 javascript 源码编译成 AST 时,还通过了一些细化的步骤
- Stram 将源码解决为对立的编码格局
- Scanner 进行词法剖析,将代码转成 token
- token 会被转换成 AST,通过 preparser 和 parser 模块
parser 用来解析定义在全局的函数和变量,定义在函数中的函数只会通过预解析 Preparser
函数中定义函数的执行程序
var user = "alice"
foo(12)
function foo(num){console.log(m)
var m = 10
function bar(){console.log(user)
}
bar()}
以上代码通过如下步骤才被执行
-
javascript –> ast
- 全局创立一个 GO(GlobalObject)对象,GO 中有很多内置模块,如 Math、String、以及 window 属性,其中 window 的值为 this,也就是本身 GO 对象
- 全局定义的变量 user 会增加 GO 对象中,并赋值为 undefined
- 函数 foo 会开拓一块内存空间,比方为 0x100,用来存储父级作用域(parent scope)和本身代码块,函数 foo 的父级作用域就是全局对象 GO
- 将 foo 增加到 GO 对象中,赋值为内存地址,如 0x100
-
ast –> Ignition
- V8 引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
- GEC 存在 VO(variable Object),在全局上下文中指向 GO 对象
-
Ignition –> 运行后果
(1)执行全局代码- 通过 VO 找到 GO
- 将 user 赋值为 alice
- 执行函数 foo 前,创立 foo 函数的执行上下文(Function Excution Context),存在 VO(variable Object)指向 AO(Activation Object)对象
- 创立 Activation Object,将 num、m 都定义为 undefined
- 为函数 bar 开拓内存空间 0x200,用来存储父级作用域和本身代码,bar 的父级作用域为函数 foo 的作用域 AO+ 全局作用域 GO
- 将 bar 增加到 foo 的 AO 对象中,赋值为内存地址,0x200
(2) 执行函数 foo
- 将 num 赋值为 12,m 赋值为 10
(3) 执行函数 bar
- 创立 bar 的执行上下文,存在 VO(variable Object)指向 AO(Activation Object)对象
- 创立 Activation Object,此时 AO 为空对象
- 函数 bar 执行实现,从调用栈(ECStack)栈顶弹出
- 函数 foo 也执行实现了,从调用栈(ECStack)栈顶弹出
所以下面代码执行后果为
undefined
alice
- m 在打印的时候还没有被赋值,所以为 undefined
- 打印 user,首先在本人作用域中查找,没有找到,往上在父级作用域 foo 的 AO 对象中查找,还没有找到,就找到了全局 GO 对象中
作用域
作用域是在解析成 AST(形象语法树)的时候确定的,与它在哪里被调用没有分割
var message = "Hello Global"
function foo(){console.log(message)
}
function bar(){
var message = "Hello Bar"
foo()}
bar()
以上代码通过如下步骤才被执行
-
javascript –> ast
- 全局创立一个 GO(GlobalObject)对象
- 全局定义的变量 message 会增加 GO 对象中,并赋值为 undefined
- 函数 foo 开拓一块内存空间,为 0x100,用来存储父级作用域(parent scope)和本身代码块,函数 foo 的父级作用域就是全局对象 GO
- 将 foo 增加到 GO 对象中,赋值为内存地址,0x100
- 函数 bar 开拓一块内存空间,为 0x200,用来存储父级作用域(parent scope)和本身代码块,函数 foo 的父级作用域就是全局对象 GO
- 将 bar 增加到 GO 对象中,赋值为内存地址,0x200
-
ast –> Ignition
- V8 引擎执行代码时,存在调用栈(ECStack),此时创立全局上下文栈(Global Excution Context)
- GEC 存在 VO(variable Object),在全局上下文中指向 GO 对象
-
Ignition –> 运行后果
(1)执行全局代码- 通过 VO 找到 GO
- 将 message 赋值为 Hello Global
- 执行函数 bar 前,创立 bar 函数的执行上下文(Function Excution Context),存在 VO(variable Object)指向 AO(Activation Object)对象
- 创立 Activation Object,将 message 定义为 undefined
(2) 执行函数 bar
- 将 message 赋值为 Hello Bar
(3) 执行函数 foo
- 创立 foo 的执行上下文,存在 VO(variable Object)指向 AO(Activation Object)对象
- 创立 Activation Object,此时 AO 为空对象
- 打印 message,此时本人作用域内没有 message,向上查找父级作用域,foo 的父级作用域为 GO
- 函数 foo 执行实现,从调用栈(ECStack)栈顶弹出
- 函数 bar 也执行实现了,从调用栈(ECStack)栈顶弹出
所以最初输入的后果为
Hello Gloabl
图示如下
易混同点
一、没有通过 var 标识符申明的变量会被增加到全局
var n = 100
function foo(){n = 200}
foo()
console.log(n)
执行过程如下
-
javascript –> ast
- GO 对象中将 n 定义为 undefined
- 开拓 foo 函数的内存空间 0x100,父级作用域为 GO
- 将 foo 增加到 GO 对象中,值为 0x100
-
ast –> Ignition
- 创立全局上下文,VO 指向 GO
- 执行 foo 函数前,创立函数上下文,VO 对象指向 AO 对象
- 创立 AO 对象,AO 为空对象
-
赋值
- GO 中的变量 n 被赋值为 100
- 执行 foo 函数中的赋值,因为没有 var 标识符申明,所以间接给全局 GO 中的 n 赋值 200
所以此时执行后果为
200
二、函数作用域内有变量,就不会向父级作用域查找
function foo(){console.log(n)
var n = 200
console.log(n)
}
var n = 100
foo()
执行程序如下
-
javascript –> ast
- GO 对象中增加变量 n,值为 undefined
- 为函数 foo 开拓内存空间 0x300,父级作用域为 GO
- 将 foo 增加到 GO 对象中,值为 0x300
-
ast —> Ignition
- 创立全局上下文,VO 指向 GO
- 执行函数 foo 之前创立函数上下文,VO 指向 AO
- 创立 AO 对象,增加变量 n,值为 undefined
-
赋值
- 将 GO 中的 n 赋值为 100
- 执行 foo,打印 n,此时先在本人的作用域内查找是否存在变量 n,AO 中存在 n 值为 undefined,所以不会再向父级作用域中查找
- 将 AO 中 n 赋值为 200
- 打印 n,此时本人作用域中存在 n,值为 200
所以执行后果为
undefined
200
三、return 语句不影响 ast 的生成
在代码解析阶段,是不会受 return 语句的影响,ast 生成的过程中,只会去查找 var 和 function 标识符定义的内容
var a = 100
function foo(){console.log(a)
return
var a = 100
console.log(a)
}
foo()
执行过程如下
-
javascript –> ast
- GO 对象中将 a 定义为 undefined
- 开拓 foo 函数的内存空间 0x400,父级作用域为 GO
- 将 foo 增加到 GO 对象中,值为 0x400
-
ast –> Ignition
- 创立全局上下文,VO 指向 GO
- 执行 foo 函数前,创立函数上下文,VO 对象指向 AO 对象
- 创立 AO 对象,将 a 增加到 AO 对象中,值为 undefined
-
赋值
- GO 中的变量 a 被赋值为 100
- 执行 foo 函数,打印 a,此时 a 没有被定义,所以输入 undefined
- 执行 return,return 前面的代码不会执行
所以执行后果为
undefined
四、连等赋值
var a = b = 10,相当于 var a = 10; b = 10
function foo(){var a = b = 10}
foo()
console.log(b)
console.log(a)
执行过程如下
-
javascript –> ast
- 创立 GO 对象,GO 对象为空
- 开拓 foo 函数的内存空间 0x500,父级作用域为 GO
- 将 foo 增加到 GO 对象中,值为 0x500
-
ast –> Ignition
- 创立全局上下文,VO 指向 GO
- 执行 foo 函数前,创立函数上下文,VO 对象指向 AO 对象
- 创立 AO 对象,将 a 增加到 AO 对象中,值为 undefined
-
赋值
- 执行 foo 函数,var a = b = 10,相当于 var a = 10; b = 10,a 变量有标识符,所以 a 被增加到 AO 对象中,赋值为 10,b 没有示意符,所以 b 被增加到全局对象 GO,赋值为 10
- 打印 b,GO 对象中能找到 b,值为 10
- 打印 a,GO 对象中没有 a,且没有父级作用域,无奈向上查找,此时报错
所以执行后果为
10
Uncaught ReferenceError: a is not defined
以上就是如何从 javascript 代码解析过程了解执行上下文与作用域晋升的具体介绍,对于 js 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~