什么是浏览器引擎?什么是JavaScript引擎?

一、浏览器内核?

不同的浏览器有不同的内核:

1、Gecko,早期被Netscape和Mozilla Firefox浏览器浏览器使用;

2、Trident,微软开发,被IE4~IE11浏览器使用,但是Edge浏览器已经转向Blink;

3、Webkit,苹果基于KHTML开发、开源的,用于Safari,Google Chrome之前也在使用;

4、Blink:是Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Opera等;

......

事实上,浏览器内核指的是浏览器的排版引擎,也称为浏览器引擎、页面渲染引擎或样板引擎

二、JavaScript引擎

在浏览器内核渲染DOM树时,我们还需要JavaScript引擎来执行JS代码,那么什么是JavaScript引擎,为什么需要JavaScript引擎?

1、为什么需要JavaScript引擎?

  • 高级编程语言都是需要转成最终的机器指令来执行  
  • 编写的JS代码不管是交给浏览器还是Node.js来执行,最终都要被【CPU】执行
  • 只有CPU自己的指令集(机器语言),才能被CPU执行
  • JavaScript引擎的目的就是为了将JS代码翻译成CPU指令去执行

2、JavaScript引擎常见的有哪些?

  • SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(也就是JavaScript作者)
  • Chakra:微软开发,用于IT浏览器
  • JavaScriptCore:WebKit中的JavaScript引擎,Apple公司开发
  • V8:Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出

3、JavaScript是一门高级编程语言

  机器语言(CPU二进制指令集) -》 汇编语言(二进制转换为能够辨认、记忆的语言) -》高级语言(屏蔽了计算机架构的细节,兼容多种不同的 CPU 架构)

  高级语言的两种执行方式:

  1、解释执行,解释执行会先将输出的源码通过解析器编译成中间代码,再间接应用解释器解释执行中间代码,输入后果。

  2、编译执行,会将源码转换为中间代码,而后编译器会将中间代码编译成机器码,通常编译成的机器码以二进制文件模式存储,执行二进制文件输入后果。编译后的机器码还能够保留在内存中,能够间接执行内存中的二进制代码。

浏览器内核负责HTML解析、布局、渲染等相关工作,JavaScript引擎负责解析、执行JavaScript代码。

三、V8引擎原理

【V8引擎】

  1、V8 是由谷歌收购并使用 C++开发并开源的 JavaScript & WebAssembly引擎,运用于 Chrome 浏览器 & Node.js,所以我们写的 JavaScript 应用,大多数都跑在 V8 上;

  2、它实现ECMAScript和WebAssembly,并在Windows 7或更高版本,MacOS 10.12+和使用x64,IA-32, ARM或MIPS处理器的Linux系统上运行;

  3、V8可以独立运行,也可以嵌入到任何C ++应用程序中;

【运行环境】

  宿主和 V8 共用同一套内存空间,在执行时,它要依赖于由宿主提供的基础环境(类似于寄生关系),大致包含了我们熟悉的全局执行上下文、事件循环系统、堆栈空间、宿主环境特殊定制的 API等。除了需要宿主提供的一些基础环境之外,V8 自身会使用创建好的堆和栈并提供 JavaScript 的核心功能(Object、Function、String)以及垃圾回收(GC)。

【引擎架构】

  V8 采用解释执行和编译执行这两种形式,这种混合应用的形式称为 JIT (即时编译),V8 在执行 JavaScript 源码时,首先解析器会将源码解析为 AST 抽象语法树,解释器 (Ignition) 会将 AST 转换为字节码,一边解释一边执行。解释器同时会记录某一代码片段的执行次数,如果执行次数超过了某个阈值,这段代码便会被标记为热代码(Hot Code),同时将运行信息反馈给优化编译器 TurboFan,TurboFan 依据反馈信息,会优化并编译字节码,最初生成优化的机器码。

【外围模块】

 V8 外围模块:

  • 解析器 Parser:解析器负责将 JavaScript 代码转换成 AST 形象语法树
  • 解释器 Ignition:解释器负责将 AST 转换为字节码,并收集 TurboFan 须要的优化编译信息
  • 编译器 TurboFan:利用解释器收集到的信息,将字节码转换为优化的机器码

 V8 须要等编译实现后才能够运行代码,所以解析和编译过程中的性能非常重要。

【解析器 Parser】

 Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码,如果函数没有被调用,那么是不会被转换成AST的。PreParse(预解析),并不是一开始所有代码都需要执行,所以V8引擎就实现了Lazy Parsing(延迟解析)的方案(惰性解析),它的作用是将不必要的函数进行预解析,也就是只解析暂 时需要的内容,而对函数的全量解析是在函数被调用时才会进行。

【解释器 Ignition】

    会将AST转换成ByteCode(字节码,字节码不仅占用内存少,而且生成字节码的工夫很快,晋升了启动速度)并执行,同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算); 如果函数只调用一次,Ignition会执行解释执行ByteCode。

   寄存器:

   解释器通常有两种类型,基于栈和基于寄存器的解释器,Ignition 解释器在执行字节码时,应用了通用寄存器和累加寄存器,函数参数和局部变量会保留在通用寄存器中,累加寄存器会保留两头计算结果。在执行指令的过程中,CPU 须要对数据进行读写,如果间接在内存中读写的话,会重大影响程序的执行性能。所以 CPU 就引入了寄存器,将一些两头数据寄存到寄存器中,晋升 CPU 的执行速度。

【编译器 TurboFan】

    可以将字节码编译为CPU可以直接执行的机器码,如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能; 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是 number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码。

四、垃圾变量回收机制GC
【JavaScript的垃圾回收机制】
    了解垃圾回收之前,我们有必要了解一下JS中的数据是如何存储的。 JS数据分为基本类型和引用类型,其中前者采用栈这个数据结构来存储,后者采用堆这个数据结构来存储,所以存储形式的不同导致了它们的回收机制也有所区别。
   1、栈回收,首先系统栈用来存储变量,它还有一个特点就是通过移动栈顶指针可以切换执行上下文,栈的回收机制比较简单,就是在切换执行上下文的时候,栈顶内容自动被回收,这就是栈的垃圾回收机制。
   2、堆回收,引用类型数据用堆来存储,在堆中,我们把存储空间分为两个部分,分别是新生代和老生代,老生代就是一些常驻内存,存活时间长,新生代就是临时分配的内存,存活时间短,且新生代内存比老生代小得多。

 V8的垃圾回收策略主要基于分代式垃圾回收机制,现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法。

   【Scavenge算法】

  在分代的基础上,新生代中的对象主要通过Scavenge算法进行垃圾回收,在Scavenge的具体 实现中,主要采用了Cheney算法

 

 

 Cheney 算法是一种采用复制的方式实现的垃圾回收算法。它将堆内存一分为二,每一部分空间称为 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。

 From表示当前正在使用的内存,To表示空闲内存。

 在垃圾回收的时候,会遍历From里的内存,将还存活的变量移动到To的内存中中,非存活的直接回收。简而言之, 在垃圾回收的过程中, 就是通过将存活对象在两个 semispace 空间之间进行复制。

   【Mark-Sweep & Mark-Compact】

    Scavenge算法通过牺牲空间换时间的算法非常适合生命周期短的新生代,但是,当一个对象经过多次复制,生命周期较长的时候或在To空间不足的时候,对象会被分配到进入到老生代中,需要采用新的算法进行垃圾回收。

  新生代内存晋升老生代的条件:

  • 经历过一次Scavenge回收
  • To内存里空间占用超过25%

 V8采用的方法就是标记法,Mark-Sweep 在标记阶段遍历堆中的所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象。Scavenge 中只复制活着的对象,而 Mark-Sweep 只清理死亡对象。然而Mark-Sweep 在进行一次标记清除回收后,内存空间会出现不连续的状态,不可避免的会出现内存碎片,V8的处理也很直接,直接统一移动到一头,移动完成后,直接清理掉边界外的内存,这些移动的工作量也是回收中最费时的一部分。

 小知识:

 为了避免出现 JavaScript 应用逻辑与垃圾回收器看到的不一致的情况,垃圾回收的 3 种基本算法都需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为“全停顿",长时间的"全停顿"垃圾回收会让用户感受到明显的卡顿,带来体验的影响。以1.5 GB的垃圾回收堆内存为例,V8做一次小的垃圾回收需要50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引起JavaScript线程暂停执行的时间,在 这样的时间花销下,应用的性能和响应能力都会直线下降。

 【增量标记】

 为了降低全堆垃圾回收带来的停顿时间,V8对此也采取了一些措施,采用增量标记的方法,其实说白了就是在标记阶段:拆分为许多小“步进”,每做完一“步进”,就让JavaScript应用逻辑执行一会儿,垃圾回收和应用逻辑交替执行直到标记阶段完成。

 

参考:

1、https://zhuanlan.zhihu.com/p/37996721

2、https://juejin.cn/post/6854573211716321287

3、https://lequ7.com/guan-yu-v8-jiao-nv-peng-you-xue-qian-duan-zhi-shen-ru-li-jie-js-yin-qing.html

4、https://blog.csdn.net/qq_36380426/article/details/114826470

5、https://cloud.tencent.com/developer/article/1710084

posted @   L大熊  阅读(966)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示