从JS执行原理开始理解作用域 —— V8引擎原理篇

278 阅读3分钟

浏览器的工作原理

我们知道浏览器在请求到静态资源后,会通过html文件构建dom树,遇到link标签会拿到css文件构建成CSSOM树;

如果遇到script标签,则判断是否含有defer或async,不然script加载和执行会造成页面的渲染阻塞。

那JS代码在浏览器中是如何被执行的呢?

JavaScript引擎原理

这时就不得不提起一个东西——JavaScript引擎。

为什么需要JavaScript引擎呢?

高级的编程语言对于CPU来说,是并不认识的,CPU只认识0 1这种机器语言,而JacaScript引擎就是将我们的JS代码编译成CPU认识的指令来执行。

比如说,我们平时去进行翻译,因为我们(CPU)看不懂这个语言,所以需要翻译app作为这JS引擎,将不懂的语言(js代码)翻译成我们认识的语言(机器语言)。

回到上述的浏览器工作原理中,我们可以知道JS代码会被JS引擎所执行;V8引擎就是一个JavaScriptCore。

那引擎是如何执行JS的代码的呢?

JS代码大致会经过以下步骤:词法分析->语法解析->代码生成。

下图为V8引擎解析图,具体可查看文档:v8.dev/blog

image.png

词法分析

它通过Scanner(扫描器)会将字符串分解成有意义的代码块(token),这个过程叫做分词。比如一个var test = 1;,会被解析成一堆token:vartest=1;

语法解析

它会将上述的代码块生成抽象语法树,也就是abstract syntax tree (AST)

上述流程由解析器Parser模块来执行,Parser模块就是V8引擎中的一个执行模块。

当然,并不是所有的JS代码在一开始就被执行,因为这会非常影响页面的运行效率。

因此,V8引擎内部就实现了一个延迟解析(Lazy Parsing)的方案,将不必要的函数进行预解析,只解析暂时需要的内容,在函数被调用时才进行函数的全量解析。预解析只验证函数语法是否有效、解析函数声明、确定函数作用域,不会生成 AST,而实现预解析的,就是 Pre-Parser 解析器。

提问:为什么需要转成AST树?

因为解释器并不直接认识JavaScript代码,需要parser模块转换成解释器(Ignition模块)能够理解的代码。

解释器转换

Ignition是一个解释器,上述我们已经生成了AST,解释器就是用来将AST转换成ByteCode(字节码)的模块,同时它会收集TurboFan优化需要的信息,比如函数参数的类型。

如果函数只执行一次,那么Ignition会执行,解释成ByteCode。

TurboFan编译

TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码。

如果一个函数被多次调用了,就会被标记成一个热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能。

同时,机器码也会被还原成ByteCode,因为如果后续执行函数的过程中,类型发生了变化,之前优化的机器码就不能正确处理运算,就会逆向的转换成字节码。

具体可参考文档:v8.dev/blog/turbof…