从我接触前端到如今,一直听到的一句话:操做DOM的成本很高,不要轻易去操做DOM。尤为是React、vue等MV*框架的出现,数据驱动视图的模式愈加深刻人心,jQuery时代提供的强大便利地操做DOM的API在前端工程里用的愈来愈少。刨根问底,这里说的成本,到底高在哪儿呢?
Document Object Model 文档对象模型
什么是DOM?可能不少人第一反应就是div、p、span等html标签(至少我是),但要知道,DOM是Model,是Object Model,对象模型,是为HTML(and XML)提供的API。HTML(Hyper Text Markup Language)是一种标记语言,HTML在DOM的模型标准中被视为对象,DOM只提供编程接口,却没法实际操做HTML里面的内容。但在浏览器端,前端们能够用脚本语言(JavaScript)经过DOM去操做HTML内容。css
那么问题来了,只有JavaScript才能调用DOM这个API吗?html
答案是NO。前端
Python也能够访问DOM。因此DOM不是提供给Javascript的API,也不是Javascript里的API。vue
PS: 实质上还存在CSSOM:CSS Object Model,浏览器将CSS代码解析成树形的数据结构,与DOM是两个独立的数据结构。node
讨论DOM操做成本,确定要先了解该成本的来源,那么就离不开浏览器渲染。
这里暂只讨论浏览器拿到HTML以后开始解析、渲染。(怎么拿到HTML资源的可能后续另开篇总结吧,什么握握握手啊挥挥挥挥手啊,万恶的flag...)git
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
不管是DOM仍是CSSOM,都是要通过
Bytes → characters → tokens → nodes → object model
这个过程。
DOM树构建过程:当前节点的全部子节点都构建好后才会去构建当前节点的下一个兄弟节点。
上述也提到了CSSOM的构建过程,也是树的结构,在最终计算各个节点的样式时,浏览器都会先从该节点的广泛属性(好比body里设置的全局样式)开始,再去应用该节点的具体属性。还有要注意的是,每一个浏览器都有本身默认的样式表,所以不少时候这棵CSSOM树只是对这张默认样式表的部分替换。github
DOM树和CSSOM树合并生成render树
简单描述这个过程:web
DOM树从根节点开始遍历可见节点,这里之因此强调了“可见”,是由于若是遇到设置了相似display: none;
的不可见节点,在render过程当中是会被跳过的(但visibility: hidden; opacity: 0
这种仍旧占据空间的节点不会被跳过render),保存各个节点的样式信息及其他节点的从属关系。chrome
有了各个节点的样式信息和属性,但不知道各个节点的确切位置和大小,因此要经过布局将样式信息和属性转换为实际可视窗口的相对大小和位置。编程
万事俱备,最后只要将肯定好位置大小的各节点,经过GPU渲染到屏幕的实际像素。
reflow(回流): 根据Render Tree布局(几何属性),意味着元素的内容、结构、位置或尺寸发生了变化,须要从新计算样式和渲染树;
repaint(重绘): 意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只须要应用新样式绘制这个元素就能够了;
reflow回流的成本开销要高于repaint重绘,一个节点的回流每每回致使子节点以及同级节点的回流;
GoogleChromeLabs 里面有一个csstriggers,列出了各个CSS属性对浏览器执行Layout、Paint、Composite的影响。
现代浏览器会对回流作优化,它会等到足够数量的变化发生,再作一次批处理回流。
display: none
,操做完再显示。(由于隐藏元素不在render树内,所以修改隐藏元素不会触发回流重绘)构建Render树须要DOM和CSSOM,因此HTML和CSS都会阻塞渲染。因此须要让CSS尽早加载(如:放在头部),以缩短首次渲染的时间。
阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML
普通的脚本会阻塞浏览器解析,加上defer或async属性,脚本就变成异步,可等到解析完毕再执行
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> <script src="app.js"></script> </body> </html>
script
标记,唤醒JavaScript解析器
,就会进行暂停 blocked
浏览器解析HTML,并等到 CSSOM
构建完毕,才执行js脚本说了这么多,其实能够总结几点浏览器首屏渲染优化的方向
其实写了这么多,感受偏题了,大量的资料参考的是chrome开发者文档。感受js脚本资源那块仍是有点乱,包括和DOMContentLoaded的关系,但愿你们能多多指点,多多批评,谢谢大佬们。
操做DOM具体的成本,说究竟是形成浏览器回流reflow和重绘reflow,从而消耗GPU资源。
https://developers.google.com/web/fundamentals/performance/critical-rendering-path/
已同步至我的博客- 软硬皆施
Github 欢迎star :)