浏览器的核心是两部分:渲染引擎和 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 代码还没下载完,但浏览器已经显示出内容了。
DOM树:浏览器把获取到的html代码解析成1个Dom树,html中的每个标签都是Dom树中的1个节点,根节点就是我们常用的document对象 。dom树里面包含了所有的html 标签,包括display:none隐藏的元素,还有用JS动态添加的元素等。
样式结构体(CSSOM):浏览器把所有样式(主要包括css和浏览器的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式。
渲染树(render tree):DOM 树和样式结构体(cssom)结合后构建呈现渲染树(render tree),渲染树有点类似于DOM树,但其实区别有很大,渲染树能识别样式,且渲染树中每个节点都有自己的style,而且渲染树不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。
注意: visibility:hidden隐藏的元素还是会包含到render tree中的,因visibility:hidden 会影响布局(layout),会占有空间。这也是与display:none;的区别所在。
重流、重绘
重流:当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。
重绘:当渲染树中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
注:回流比重绘的代价要更高,回流必将引起重绘,而重绘不一定会引起回流。
回流、重绘发生场景
不同的条件下发生重排的范围及程度会不同 :
-
页面初始渲染
-
改变字体,改变元素尺寸(宽、高、内外边距、边框,改变元素位置等
-
设置 style 属性的值
-
激活 CSS 伪类,比如 :hover
-
操作 class 属性
-
css3的某些属性(csstriggers.com/ 结合此链接查看哪些属性会触发重排、哪些属性会触发重绘以及哪些属性会触发合成;)
-
-
改变元素内容(文本或图片等或比如用户在input框中输入文字)
-
添加/删除可见DOM元素(注意:如果是删除本身就display:none的元素不会发生重排;visibility:hidden的元素显示或隐藏不影响重排)
-
fixed定位的元素,在拖动滚动条的时候会一直回流
-
调整窗口大小(Resizing the window)
-
计算 offsetWidth 和 offsetHeight 属性
浏览器是聪明的,当对以下属性进行操作的时候:
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)
浏览器不会马上操作它们,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。但是在这过程中我们需要去获取在该队列中的属性时,浏览器为取得正确的值就会触发重排。这样就使得浏览器的优化失效了。所以,在多次使用这些值时应进行缓存。
减少回流、重绘
使用class替换样式,尽量不要使用js操作样式
csstext(利用cssText属性合并所有改变,然后一次性写入)
读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
缓存 DOM 信息。
使用
documentFragment
操作 DOM动画使用
absolute
定位或fixed
定位,这样可以减少对其他元素的影响。只在必要时才显示隐藏元素。
使用
window.requestAnimationFrame()
,因为它可以把代码推迟到下一次重绘之前执行,而不是立即要求页面重绘。使用虚拟 DOM(virtual DOM)库。
使用trsansform:
- CSS的最终表现分为以下四步:Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers
- 按照中文的意思大致是 查找并计算样式 -> 排布 -> 绘制 -> 组合层。
- 由于transform是位于组合层,而width、left、margin等则是位于排布层,在排布层发生的改变必定导致绘制 -> 组合层
- 所以相对而言使用transform实现的动画效果肯定比使用改变位置(margin-left等)这些更加流畅
JavaScript 引擎
JavaScript 引擎的主要作用是,读取网页中的 JavaScript 代码,对其处理后运行。
JavaScript 是一种解释型语言,也就是说,它不需要编译,由解释器实时运行。这样的好处是运行和修改都比较方便,刷新页面就可以重新解释;缺点是每次运行都要调用解释器,系统开销较大,运行速度慢于编译型语言。
为了提高运行速度,目前的浏览器都将 JavaScript 进行一定程度的编译,生成类似字节码(bytecode)的中间代码,以提高运行速度。
早期,浏览器内部对 JavaScript 的处理过程如下:
- 读取代码,进行词法分析(Lexical analysis),将代码分解成词元(token)。
- 对词元进行语法分析(parsing),将代码整理成“语法树”(syntax tree)。
- 使用“翻译器”(translator),将代码转为字节码(bytecode)。
- 使用“字节码解释器”(bytecode interpreter),将字节码转为机器码。
逐行解释将字节码转为机器码,是很低效的。为了提高运行速度,现代浏览器改为采用“即时编译”(Just In Time compiler,缩写 JIT),即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)。通常,一个程序被经常用到的,只是其中一小部分代码,有了缓存的编译结果,整个程序的运行速度就会显著提升。
字节码不能直接运行,而是运行在一个虚拟机(Virtual Machine)之上,一般也把虚拟机称为 JavaScript 引擎。并非所有的 JavaScript 虚拟机运行时都有字节码,有的 JavaScript 虚拟机基于源码,即只要有可能,就通过 JIT(just in time)编译器直接把源码编译成机器码运行,省略字节码步骤。这一点与其他采用虚拟机(比如 Java)的语言不尽相同。这样做的目的,是为了尽可能地优化代码、提高性能。下面是目前最常见的一些 JavaScript 虚拟机:
- Chakra (Microsoft Internet Explorer)
- Nitro/JavaScript Core (Safari)
- Carakan (Opera)
- SpiderMonkey (Firefox)
- V8 (Chrome, Chromium)