浏览器的工作原理
我们知道浏览器在请求到静态资源后,会通过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
词法分析
它通过Scanner(扫描器)会将字符串分解成有意义的代码块(token),这个过程叫做分词。比如一个var test = 1;,会被解析成一堆token:var、test、=、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…