如何高效的操作DOM
明白了 DOM 操作有哪些消耗性能问题后,要提升性能就变得很简单了,反其道而行之,减少这些操作即可。
在循环外操作元素
比如下面两段测试代码对比了读取 1000 次 JSON 对象以及访问 1000 次 body 元素的耗时差异,相差一个数量级。
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
当然即使在循环外也要尽量减少操作元素,因为不知道他人调用你的代码时是否处于循环中。
批量操作元素
比如说要创建 1 万个 div 元素,在循环中直接创建再添加到父元素上耗时会非常多。如果采用字符串拼接的形式,先将 1 万个 div 元素的 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')
如果将需要修改的样式属性放入 JavaScript 数组,然后对这些修改进行 reduce 操作,得到最终需要的样式之后再设置元素属性,那么性能会提升很多。代码如下:
const times = 20000; let html = '' for (let i = 0; i < times; i++) { html = `<div>${i}${html}</div>` } document.body.innerHTML += html let queue = [] // 创建缓存样式的数组 let microTask // 执行修改样式的微任务 const st = () => { const div = document.querySelector('div') // 合并样式 const style = queue.reduce((acc, cur) => ({...acc, ...cur}), {}) for(let prop in style) { div.style[prop] = style[prop] } queue = [] microTask = null } const setStyle = (style) => { queue.push(style) // 创建微任务 if(!microTask) microTask = Promise.resolve().then(st) } for (let i = 0; i < times; i++) { const style = { fontSize: (i % 12) + 12 + 'px', color: i % 2 ? 'red' : 'green', margin: (i % 12) + 12 + 'px' } setStyle(style) }
virtualDOM 之所以号称高性能,其实现原理就与此类似。
缓存元素集合
比如将通过选择器函数获取到的 DOM 元素赋值给变量,之后通过变量操作而不是再次使用选择器函数来获取。
下面举例说明,假设我们现在要将上面代码所创建的 1 万个 div 元素的文本内容进行修改。每次重复使用获取选择器函数来获取元素,代码如下:
for (let i = 0; i < document.querySelectorAll('div').length; i++) { document.querySelectorAll(`div`)[i].innerText = i }
如果能够将元素集合赋值给 JavaScript 变量,每次通过变量去修改元素,那么性能将会得到不小的提升。
const divs = document.querySelectorAll('div') for (let i = 0; i < divs.length; i++) { divs[i].innerText = i }
除了这些方法之外,还有一些原则也可能帮助我们提升渲染性能,比如:
- 尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成 CSSOM 树的时间;
- 尽量减少重排和重绘影响的区域;
- 使用 CSS3 特性来实现动画效果。