在浏览器中存在着渲染引擎,而v8只是当中用于处理js代码的部分,在代码执行之前会经历编译过程,作用域和作用域链确认都是在编译阶段就已经完成的。
以一段 js 为例,看看 v8 引擎是如何解析的
const username = 'alishi
- 渲染引擎 => utf-8 chunks
- stream => utf-16 code utils
- Scanner (扫描器): 经过词法分析之后得到的 tokens 可以表示如下
[
{"type": "Keyword", "value": "const"},
{"type": "Identifier", "value": "username"},
{"type": "Punctuator", "value": "="},
{"type": "String", "value": "alishi"},
]
- PreParser (预解析): 比如一些定义了但是没有使用的变量和方法就会采用预解析,而不是全量解析。帮助我们跳过未被使用的代码,不生成AST,创建无变量引用和声明的 scopes(作用域),使解析速度更快。
- Parser (解析器): 经过语法分析之后得到 AST(抽象语法树),同时会进行语法校验,抛出所有的语法错误,并构建具体的scopes信息(变量引用声明等)
{
"type": "Keyword",
"body": [
{
"type": "VaroableDeclaration",
"declarations": [
{
"type": "VaroableDeclarator",
"id": {
"type": "Identifier",
"name": "username",
},
"init": {
"type": "Literal",
"value": "alishi",
"raw": "alishi",
},
}
],
"kind": "const",
}
],
"sourceType": "script",
}
// 声明时未调用,因此会被认为是不被执行的代码,进行预解析
function foo() {
...
}
// 声明时未调用,因此会被认为是不被执行的代码,进行预解析
function fn() {
...
}
// 函数立即执行,只进行一次全量解析
(function bar() {
...
})()
// 执行 foo,需要重新对 foo 函数进行全量解析,此时 foo 函数被解析了两次
// 如果 foo 里面调用了其他函数,那么这个函数也会被解析两次,所以要尽量避免函数嵌套
foo()
- Ignition (解释器) 将 AST 转为字节码 (bytecode)
- TurboFan (编译器) => 机器码
最后拿到机器码之后就可以执行代码了,进行堆栈处理。
学习自拉钩教育前端视频