浏览器的组成
浏览器的核心是两部分:渲染引擎和 JavaScript 解释器(又称 JavaScript 引擎)。
渲染引擎
不同的浏览器有不同的渲染引擎。
Firefox:Gecko 引擎,Safari:WebKit 引擎,Chrome:Blink 引擎,IE: Trident 引擎,Edge: EdgeHTML 引擎
渲染过程四个阶段。
- 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
- 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
- 布局:计算出渲染树的布局(layout)。
- 绘制:将渲染树绘制到屏幕。
以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的 HTML 代码还没下载完,但浏览器已经显示出内容了。
重绘和回流
渲染树转换为布局,称为“布局流”(flow);布局显示到页面的这个过程,称为“绘制”(paint)。它们都具有阻塞效应,并且会耗费很多时间和计算资源。
页面生成以后,脚本操作和样式表操作,都会触发“重流”(reflow)和“重绘”(repaint)。再比如,重绘 table 布局和 flex 布局,开销都会比较大。
优化:
- 读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
- 缓存 DOM 信息。
- 一次性改变样式。
- 使用 documentFragment 操作 DOM
- 动画使用 absolute 定位或 fixed 定位,这样可以减少对其他元素的影响。
- 只在必要时才显示隐藏元素。
- 使用 window.requestAnimationFrame(),因为它可以把代码推迟到下一次重绘之前执行,而不是立即要求页面重绘。
- 使用虚拟 DOM(virtual DOM)库。
JavaScript 引擎
早期,JavaScript 处理过程如下:
- 读取代码,进行词法分析(Lexical analysis),将代码分解成词元(token)。
- 对词元进行语法分析(parsing),将代码整理成“语法树”(syntax tree)。
- 使用“翻译器”(translator),将代码转为字节码(bytecode)。
- 使用“字节码解释器”(bytecode interpreter),将字节码转为机器码。
现代浏览器改为采用“即时编译”(Just In Time compiler,缩写 JIT),即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)。
字节码不能直接运行,而是运行在一个虚拟机(Virtual Machine)之上,一般也把虚拟机称为 JavaScript 引擎。
网页中嵌入 JavaScript 代码,主要有四种方法。
- <script> 元素直接嵌入代码。
- <script> 标签加载外部脚本
- 事件属性
- URL 协议
工作原理
浏览器加载 JavaScript 脚本,主要通过<script>元素完成。正常的网页加载流程是这样的。
- 浏览器一边下载 HTML 网页,一边开始解析。也就是说,不等到下载完,就开始解析。
- 解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给 JavaScript 引擎。
- 如果<script>元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。
- JavaScript 引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页。
优化
-
defer 属性
继续完成解析 HTML 后,执行下载好的 js。
<script src="a.js" defer></script>
<script src="b.js" defer></script>
-
async 属性
继续往下解析 HTML,js 下载完成,立即执行,继续解析 html。
::: tip 提示 同时使用 async 和 defer 属性,浏览器行为由 async 属性决定。 :::
参考链接
https://wangdoc.com/javascript/bom/engine#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5