关于DOM
DOM(Document Object Model,文档对象模型)是 JavaScript 操作 HTML 的接口。
常见的DOM操作场景
- 动态渲染列表、表格表单数据;
- 监听点击、提交事件;
- 懒加载一些脚本或样式文件;
- 实现动态展开树组件,表单组件级联等这类复杂的操作。
DOM的组成
- DOM 节点
- DOM 事件
- 选择区域
DOM节点与标签和元素的区别
- 标签是 HTML 的基本单位,比如 p、div、input;
- 节点是 DOM 树的基本单位,有多种类型,比如注释节点、文本节点;
- 元素是节点中的一种,与 HTML 标签相对应,比如 p 标签会对应 p 元素。
浏览器的工作机制
我一直都听说过很多浏览器优化原则,例如说减少DOM操作,使用transformX(0)进行硬件优化,避免js文件执行时间过长使得页面卡顿等等。但是为何要如此优化,浏览器的工作原理又是如何,对我来说其实不知其所以然。其实根据浏览器工作机制,影响其性能的主要因素可以归结为线程切换和重新渲染。而在了解这两个因素之前,我们先大概的区分一下线程和进程。
线程和进程的区别
- 进程是系统资源分配的最小单位(即系统以进程为最小单位分配内存空间,同时进程是能独立运行的最小单位);
- 线程是系统调度的最小单位(即系统以线程为单位分配cpu中的核。)
- 进程之间也能互相通信,不过代价比较大。
线程切换
首先,明确的是浏览器是多线程的,但是浏览器包含的渲染引擎恶化JavaScript引擎,是单线程运行的,且是具有互斥性。单线程的优势是开发方便,避免多线程下的死锁、竞争等问题,劣势是失去了并发能力。
操作系统在进行线程切换的时候需要保存上一个线程执行时的状态信息并读取下一个线程的状态信息,俗称上下文切换。而这个操作相对而言是比较耗时的。每次 DOM 操作就会引发线程的上下文切换——从 JavaScript 引擎切换到渲染引擎执行对应操作,然后再切换回 JavaScript 引擎继续执行,这就带来了性能损耗。单次切换消耗的时间是非常少的,但是如果频繁的大量切换,那么就会产生性能问题。
重新渲染
另一个更加耗时的因素是元素及样式变化引起的再次渲染,在渲染过程中最耗时的两个步骤为重排(Reflow)与重绘(Repaint)。
浏览器渲染页面的过程
- 将 HTML 和 CSS 分别解析成 DOM 树和 CSSOM 树;
- 合并及排布;
- 绘制。
如果在操作 DOM 时涉及到元素、样式的修改,就会引起渲染引擎重新计算样式生成 CSSOM 树,同时还有可能触发对元素的重新排布(简称“重排”)和重新绘制(简称“重绘”)。
如下是可能引起重排,继而引发重绘的操作:
- 修改元素边距、大小
- 添加、删除元素
- 改变窗口大小
如下是只会引起重绘的操作:
- 设置背景图片
- 修改字体颜色
- 改变 visibility 属性值
通过一些例子,我们知道了,重排是比较耗时的操作。那么我们改如何优化呢?
如何高效操作 DOM
- 循环外操作元素
以下是循环内和循环外的差异:
const times = 10000;
console.time('switch')
for (let i = 0; i < times; i++) {
document.body === 1 ? console.log(1) : void 0;
}
console.timeEnd('switch') // 1.873046875ms
var body = JSON.stringify(document.body)
console.time('batch')
for (let i = 0; i < times; i++) {
body === 1 ? console.log(1) : void 0;
}
console.timeEnd('batch') // 0.846923828125ms
- 批量操作元素
将操作元素的html字符串凭借成一个完整字符串,然后赋值给body元素的innerHTML 属性。
const times = 10000;
console.time('createElement')
for (let i = 0; i < times; i++) {
const div = document.createElement('div')
document.body.appendChild(div)
}
console.timeEnd('createElement')// 54.964111328125ms
console.time('innerHTML')
let html=''
for (let i = 0; i < times; i++) {
html+='<div></div>'
}
document.body.innerHTML += html // 31.919921875ms
console.timeEnd('innerHTML')
- 缓存元素集合
比如将通过选择器函数获取到的 DOM 元素赋值给变量,之后通过变量操作而不是再次使用选择器函数来获取。
扩展
有一些原则也可能帮助我们提升渲染性能,比如:
- 尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成 CSSOM 树的时间;
- 尽量减少重排和重绘影响的区域;
- 使用 CSS3 特性来实现动画效果。
笔记内容来自拉勾教育朱德龙老师讲解的 前端高手进阶 第二讲