DOM 操作成本到底高在哪儿?
从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM。尤其是React、vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQuery时代提供的强大便利地操作DOM的API在前端工程里用的越来越少。刨根问底,这里说的成本,到底高在哪儿呢?
什么是DOM
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内容。
那么问题来了,只有JavaScript才能调用DOM这个API吗?
答案是NO。
Python也可以访问DOM。所以DOM不是提供给Javascript的API,也不是Javascript里的API。
PS: 实质上还存在CSSOM:CSS Object Model,浏览器将CSS代码解析成树形的数据结构,与DOM是两个独立的数据结构。
浏览器渲染过程
讨论DOM操作成本,肯定要先了解该成本的来源,那么就离不开浏览器渲染。
这里暂只讨论浏览器拿到HTML之后开始解析、渲染。(怎么拿到HTML资源的可能后续另开篇总结吧,什么握握握手啊挥挥挥挥手啊,万恶的flag...)
解析HTML,构建DOM树(这里遇到外链,此时会发起请求)
解析CSS,生成CSS规则树
合并DOM树和CSS规则,生成render树
布局render树(Layout/reflow),负责各元素尺寸、位置的计算
绘制render树(paint),绘制页面像素信息
浏览器会将各层的信息发送给GPU,GPU将各层合成(composite),显示在屏幕上
1.构建DOM树
<html> |
无论是DOM还是CSSOM,都是要经过 Bytes→characters→tokens→nodes→objectmodel
这个过程。
DOM树构建过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
2.构建CSSOM树
上述也提到了CSSOM的构建过程,也是树的结构,在最终计算各个节点的样式时,浏览器都会先从该节点的普遍属性(比如body里设置的全局样式)开始,再去应用该节点的具体属性。还有要注意的是,每个浏览器都有自己默认的样式表,因此很多时候这棵CSSOM树只是对这张默认样式表的部分替换。
3.生成render树
DOM树和CSSOM树合并生成render树
简单描述这个过程:
DOM树从根节点开始遍历可见节点,这里之所以强调了“可见”,是因为如果遇到设置了类似 display:none;
的不可见节点,在render过程中是会被跳过的(但 visibility:hidden;opacity:0
这种仍旧占据空间的节点不会被跳过render),保存各个节点的样式信息及其余节点的从属关系。
4.Layout 布局
有了各个节点的样式信息和属性,但不知道各个节点的确切位置和大小,所以要通过布局将样式信息和属性转换为实际可视窗口的相对大小和位置。
5.Paint 绘制
万事俱备,最后只要将确定好位置大小的各节点,通过GPU渲染到屏幕的实际像素。
Tips
在上述渲染过程中,前3点可能要多次执行,比如js脚本去操作dom、更改css样式时,浏览器又要重新构建DOM、CSSOM树,重新render,重新layout、paint;
Layout在Paint之前,因此每次Layout重新布局(reflow 回流)后都要重新出发Paint渲染,这时又要去消耗GPU;
Paint不一定会触发Layout,比如改个颜色改个背景;(repaint 重绘)
图片下载完也会重新出发Layout和Paint;
何时触发reflow和repaint
reflow(回流): 根据Render Tree布局(几何属性),意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树;
repaint(重绘): 意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了;
reflow回流的成本开销要高于repaint重绘,一个节点的回流往往回导致子节点以及同级节点的回流;
GoogleChromeLabs 里面有一个csstriggers,列出了各个CSS属性对浏览器执行Layout、Paint、Composite的影响。
引起reflow回流
现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。
页面第一次渲染(初始化)
DOM树变化(如:增删节点)
Render树变化(如:padding改变)
浏览器窗口resize
获取元素的某些属性:
浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle
引起repaint重绘
reflow回流必定引起repaint重绘,重绘可以单独触发
背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)
优化reflow、repaint触发次数
避免逐个修改节点样式,尽量一次性修改
使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
可以将需要多次修改的DOM元素设置
display:none
,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)避免多次读取某些属性(见上)
将复杂的节点元素脱离文档流,降低回流成本
为什么一再强调将css放在头部,将js文件放在尾部
DOMContentLoaded 和 load
DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片...
load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成
CSS 资源阻塞渲染
构建Render树需要DOM和CSSOM,所以HTML和CSS都会阻塞渲染。所以需要让CSS尽早加载(如:放在头部),以缩短首次渲染的时间。
JS 资源
阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML。
这和之前文章提到的浏览器线程有关,浏览器中js引擎线程和渲染线程是互斥的,详见《从setTimeout-setInterval看JS线程》
普通的脚本会阻塞浏览器解析,加上defer或async属性,脚本就变成异步,可等到解析完毕再执行。
async异步执行,异步下载完毕后就会执行,不确保执行顺序,一定在onload前,但不确定在DOMContentLoaded事件的前后
defer延迟执行,相对于放在body最后(理论上在DOMContentLoaded事件前)
举个栗子
<html> |
浏览器拿到HTML后,从上到下顺序解析文档
此时遇到css、js外链,则同时发起请求
开始构建DOM树
这里要特别注意,由于有CSS资源,CSSOM还未构建前,会阻塞js(如果有的话)
无论JavaScript是内联还是外链,只要浏览器遇到
script
标记,唤醒JavaScript解析器
,就会进行暂停blocked
浏览器解析HTML,并等到CSSOM
构建完毕,才执行js脚本渲染首屏(DOMContentLoaded 触发,其实不一定是首屏,可能在js脚本执行前DOM树和CSSOM已经构建完render树,已经paint)
首屏优化Tips
说了这么多,其实可以总结几点浏览器首屏渲染优化的方向:
减少资源请求数量(内联亦或是延迟动态加载)
使CSS样式表尽早加载,减少@import的使用,因为需要解析完样式表中所有import的资源才会算CSS资源下载完
异步js:阻塞解析器的 JavaScript 会强制浏览器等待 CSSOM 并暂停 DOM 的构建,导致首次渲染的时间延迟
so on...
知道操作DOM成本多高了吗?
其实写了这么多,感觉偏题了,大量的资料参考的是chrome开发者文档。感觉js脚本资源那块还是有点乱,包括和DOMContentLoaded的关系,希望大家能多多指点,多多批评,谢谢大佬们。
操作DOM具体的成本,说到底是造成浏览器回流reflow和重绘reflow,从而消耗GPU资源。
DOM 操作成本到底高在哪儿?的更多相关文章
- DOM 操作成本究竟有多高,HTML、CSS构建过程 ,从什么方向出发避免重绘重排)
前言: 2019年!我准备好了 正文:从我接触前端到现在,一直听到的一句话:操作DOM的成本很高,不要轻易去操作DOM.尤其是React.vue等MV*框架的出现,数据驱动视图的模式越发深入人心,jQ ...
- 迷你MVVM框架 avalonjs 沉思录 第2节 DOM操作的三大问题
jQuery之所以击败Prototype.js,是因为它自一开始就了解这三大问题,并提出完善的解决方案. 第一个问题,DOM什么时候可用.JS不像C那样有一个main函数,里面的逻辑不分主次.但JS是 ...
- 前端性能优化--为什么DOM操作慢? 浅谈DOM的操作以及性能优化问题-重绘重排 为什么要减少DOM操作 为什么要减少操作DOM
前端性能优化--为什么DOM操作慢? 作为一个前端,不能不考虑性能问题.对于大多数前端来说,性能优化的方法可能包括以下这些: 减少HTTP请求(合并css.js,雪碧图/base64图片) 压缩( ...
- 从DOM操作看Vue&React的前端组件化,顺带补齐React的demo
前言 接上文:谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo 上次写完博客后,有朋友反应第一内容有点深,看着迷迷糊糊:第二是感觉没什么使用场景,太过业务化,还不如直接写Vue ...
- 前端页面卡顿?或是DOM操作惹的祸,需优化代码
文档对象模型(DOM)是一个独立 于特定语言的应用程序接口.在浏览器中,DOM接口是以JavaScript语言实现的,通过JavaScript来操作浏览器页面中的元素,这使得 DOM成为了JavaSc ...
- jQuery的DOM操作详解
DOM(Document Object Model-文档对象模型):一种与浏览器, 平台, 语言无关的规则, 使用该接口可以轻松地访问页面中所有的标准组件DOM操作的分类 核心-DOM: DOM Co ...
- [译]AngularJS中DOM操作
再翻译一篇干货短文,原文:AngularJS jQuery 虽然Angularjs将我们从DOM的操作中解放出来了,但是很多时候我们还是会需要在controller/view加载之后执行一些DOM操作 ...
- javascript DOM 操作 attribute 和 property 的区别
javascript DOM 操作 attribute 和 property 的区别 在做 URLRedirector 扩展时,注意到在使用 jquery 操作 checkbox 是否勾选时,用 at ...
- Javascript的DOM操作 - 你真的了解吗?
摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...
随机推荐
- 一篇文章带您读懂List集合(源码分析)
今天要分享的Java集合是List,主要是针对它的常见实现类ArrayList进行讲解 内容目录 什么是List核心方法源码剖析1.文档注释2.构造方法3.add()3.remove()如何提升Arr ...
- Swift和Objective-C中的协议(protocol)有什么异同
Swift和Objective-C中的protocol的相同点在于:两者可以被用作代理.Objective-C中的protocol类似于Java中的Interface,在实际开发中主要用与适配器模式( ...
- java网络编程——socket实现简单的CS会话
还记得当年学计网课时用python实现一个简单的CS会话功能,这也是学习socket网络编程的第一步,现改用java重新实现,以此记录. 客户端 import java.io.*; import ja ...
- OpenFlow(OVS)下的“路由技术”
前言 熟悉这款设备的同学,应该也快到不惑之年了吧!这应该是Cisco最古老的路由器了.上个世纪80年代至今,路由交换技术不断发展,但是在这波澜壮阔的变化之中,总有一些东西在嘈杂的机房内闪闪发光,像极了 ...
- Kafka体系架构详细分解
我的个人博客排版更舒服: https://www.luozhiyun.com/archives/260 基本概念 Kafka 体系架构 Kafka 体系架构包括若干 Producer.若干 Broke ...
- 从0到1使用MyBatis
MyBatis作为最流行的数据中间层,成为企业Java软件开发中非常重要的软件. 一.基本配置 1.首先需要导入Maven <dependency> <groupId>org. ...
- JS的类
JS在创建之初不支持类,因为很多开发者为处理类创建了好多代码库,最终导致ES6引入了类. ES5及更早的版本都不支持类,与类最接近的是:创建一个构造器,然后将方法指派到该构造器的原型上.就是原型继承. ...
- [每日一题系列] LeetCode 1071. 字符串的最大公因子
题目 对于字符串 S 和 T,只有在 S = T + ... + T(T 与自身连接 1 次或多次)时,我们才认定 "T 能除尽 S". 返回最长字符串 X,要求满足 X 能除尽 ...
- Python进阶练习与爬取豆瓣T250的影片相关信息
(一)Python进阶练习 正所谓要将知识进行实践,才会真正的掌握 于是就练习了几道题:求素数,求奇数,求九九乘法表,字符串练习 import re #求素数 i=1; flag=0 while(i& ...
- win下安装virtualenv和创建django项目
一.由于一直在Linux环境下开发,想了解一下winPython开发环境: 1.打开cmd,pip install virtualenv 2.virtualenv test 由于这样需要进入到目录下才 ...