js代码导致网页崩溃延伸——如何高效操作DOM元素?

最近的项目成果发现总是会在一段时间后崩溃,定位到原因,是一个定时器那块。

起初以为是定时器导致页面崩溃,后来发现是定时器中大量的dom操作导致的页面崩溃。

总结心得:在进行页面编写时,应该尽量避免大规模的dom操作,尽量少地小规模地更改dom结构。

以上,我认为我需要深入了解一下dom操作频率与页面性能的联系。

如何高效操作DOM元素?

 

Ques:为什么dom操作耗时?

About 浏览器的工作机制:

【1】线程切换

浏览器的两个引擎:JavaScript引擎和渲染引擎 —— 具有互斥性(不能同时工作),由此产生了 线程切换(上下文切换),这一操作相对而言是比较耗时的,频繁大量的线程切换就会造成性能损耗。

【2】重新渲染

重排(Reflow)与 重绘(RePaint)

浏览器在渲染页面时,解析操作:

HTML ——> DOM树;CSS ——>  CSSOM树;

DOM操作中涉及到元素、样式的修改会引起渲染引擎重新计算样式,生成新的CSSOM树。

 

可能会影响到其他元素排布的操作就会引起 重排,继而引发 重绘,比如:

  • 修改元素边距、大小;
  • 添加、删除元素;
  • 改变窗口大小;

 

引起 重绘 的操作:

  • 设置背景图片;
  • 改变字体颜色;
  • 改变visibility属性值;

for more:https://csstriggers.com/

经测试可知,重排渲染耗时明显高于重绘 

 

得出结论:

 

(1)在循环外操作DOM & 批量操作DOM

DOM操作时最好是先循环拼接dom的字符串,再挂到页面节点上(innerHtml()),避免多次页面重排、重绘;

虽然通过修改 innerHTML 来实现批量操作的方式效率很高,但它并不是万能的。比如要在此基础上实现事件监听就会略微麻烦,只能通过事件代理或者重新选取元素再进行单独绑定。

批量操作除了用在创建元素外也可以用于修改元素属性样式。如果将需要修改的样式属性放入 JavaScript 数组,然后对这些修改进行 reduce 操作,得到最终需要的样式之后再设置元素属性,那么性能会提升很多。(设置元素属性是完成一次任务,多次设置则是看做完成多个任务,完成n次任务会进行n次渲染,造成多余时耗,参见我之前的文章,详解js单线程及异步原理&js事件循环机制:https://blog.csdn.net/ganle/article/details/106669083)

const times = 20000;
let html = ''
for (let i = 0; i < times; i++) {
  html = `<div>${i}${html}</div>`
}
document.body.innerHTML += html
const div = document.querySelector('div')
for (let i = 0; i < times; i++) {
  div.style.fontSize = (i % 12) + 12 + 'px'
  div.style.color = i % 2 ? 'red' : 'green'
  div.style.margin = (i % 12) + 12 + 'px'
}

改为:

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 // 执行修改样式的微任务

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);
}


const setStyle = (style) => {
  queue.push(style);
  // 创建微任务
  if(!microTask) microTask = Promise.resolve().then(st);
}

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;
}


代码修改后,可以减少渲染耗时;virtualDOM 之所以号称高性能,其实现原理就与此类似。

 

(2)缓存元素集合

比如将通过选择器函数获取到的 DOM 元素赋值给变量,之后通过变量操作而不是再次使用选择器函数来获取。

because:减少对DOM数的遍历操作,能明显提升性能!

 

(3)尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成 CSSOM 树的时间;

 

(4)尽量减少重排和重绘影响的区域;

 

(5)使用 CSS3 特性来实现动画效果。

 

(6)利用绝对定位脱离文档流,这样操作定位里面的内容不会引起外部的重排;

 

(7)开启子线程处理复杂计算,待计算结束后通知主线程进行dom操作;

 

(8)有动画的话,也可以考虑分层渲染的机制。加上will-change(参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/will-change

 

更多关于Web前端的性能优化问题可参考:

https://www.jianshu.com/p/3a1ee54d9496

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值