1 JavaScript运行原理
1.1 浏览器的工作原理
JavaScript代码在浏览器中是如何被执行的?
1.2 浏览器的内核
那么,向服务器申请了相干文件并下载了之后,是什么来解析咱们的js文件的呢?答案是浏览器内核。
实际上,咱们常常说的浏览器内核指的是浏览器的排版引擎:
- 排版引擎(layout engine)也称为浏览器引擎(browser engine)、页面渲染引擎(rendering engine)或样板引擎
不同的浏览器有不同的内核
- Gecko
- Trident
- Webkit
- Blink
1.3 浏览器渲染过程
如果在执行这个过程中,HTML解析的时候遇到了JavaScript标签,应该怎么办呢?
- 会进行解析HTML,而去加载和执行JavaScript代码
那么JavaScript代码由谁来执行呢?
- JavaScript引擎
1.4 JavaScript引擎
为什么须要JavaScript引擎?
- 高级的编程语言都须要转成最终的机器指令来执行
- 实际上咱们编写的JavaScript无论是交给浏览器或者Node执行最初是都须要被CPU执行的
- CPU只意识本人的指令集,只有机器语言能力被CPU所执行
- 所以须要JavaScript引擎帮忙咱们将JavaScript代码翻译成CPU指令能力被执行
常见的JavaScript引擎:
- SpiderMonkey
- Chakra
- JavaScriptCode
- V8:Google开发的JS引擎,帮忙Chrome从泛滥浏览器中怀才不遇
1.5 浏览器内核和JS引擎的关系
以WebKit为例,WebKit事实上由两局部组成:
- WebCore:负责HTML解析、布局、渲染等等相干的工作
- JavaScriptCore:解析、执行JavaScript代码
小程序也是相似的,将代码分为两层:
- Webview渲染层:负责渲染HTML构造等
- JsCore逻辑层:负责解析执行JavaScript代码
1.6 V8引擎
官网的定义:
- V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等
- 能够在很多不同的操作系统上运行
- V8能够独立运行,也能够嵌入到任何C++应用程序中
V8引擎自身的源码非常复杂,大略有超过100w行C++代码,通过理解它的架构,咱们能够晓得它是如何对JavaScript执行的:
Parse
模块:进行词法剖析和语法分析,将代码转换成<u>AST(形象语法树)</u>,因为解析器并不间接意识JS代码
- 如果函数没有被调用,那么是不会被转换成AST的
- Parse的V8官网文档:https://v8.dev/blog/scanner
Ignition
解释器:会将AST转换成ByteCode(字节码)
- 同时会收集TurboFan优化所须要的信息(比方函数参数的类型信息,有了类型能力进行实在的运算)
- 如果函数只调用一次,Ignition会执行解释去执行ByteCode
- pIgnition的V8官网文档:https://v8.dev/blog/ignition-interpreter
- 字节码是能够跨平台的,所以当运行的时候cpu会将字节码转成相干平台的汇编代码,再转换成cpu指令
TurboFan
编译器:能够将字节码编译为CPU能够间接执行的机器码
- 如果一个函数被屡次调用,会被标记为热点函数,那么就会通过TurboFan转换成优化的机器码,进步代码的执行性能
- 然而机器码实际上也会被还原为ByteCode。这是因为如果后续执行函数的过程中,类型产生了变动(比方sum函数原来执行的是number类型,起初执行变成了string类型),之前优化的机器码并不能正确地解决运算,就会逆向地转换成字节码(
Deoptimization
) - TurboFan的V8官网文档:https://v8.dev/blog/turbofan-jit
1.7 V8执行过程的细节
JavaScript源码是如何被解析(parse过程)的呢?
Blink
(内核)将JS代码交给V8引擎,Stream
获取到JS代码并且进行编码转换Scanner
(扫描器)会进行词法剖析(lexical analysis),词法剖析会将代码转换成tokens- 接下来tokens会通过
Parser
和Preparser
,被转换成AST树
Parser
就是间接将tokens转成AST树架构PreParser
称为预解析,为什么须要预解析呢?- 因为不是所有的JavaScript代码在一开始时就会被执行,那么对所有代码进行解析必然会影响网页的运行效率
- 所以V8引擎就实现了Lazy Parsing(提早解析)的计划,它的作业是将不必要的函数进行预解析,也就是只解析临时须要的内容。而对函数的全量解析是在函数被调用时才会进行
- 生成的AST树只会被
Ignition
转成字节码 - 之后的过程就是代码的执行过程