《高性能JavaScript》读书笔记——DOM操作
来源:belllee     阅读:613
动云科技
发布于 2019-01-19 18:38
查看主页

用JS进行DOM操作的代价是昂贵的,它是富web应用中最常见的性能瓶颈。

DOM

文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。通常在浏览器中DOM和JS都是独立的,由于彼此独立,所以JS操作DOM,性能开销就很大。

提升性能最佳实践

//循环操作DOM15000次function innerHTMLLoop(){    for(var count = 0; count < 15000; count++){        document.getElementById('here').innerHTML += 'a';    }}//只操作1次DOM,在IE8中性能提升273倍function innerHTMLLoop(){    var content = '';    for(var count = 0; count < 15000; count++){        content += 'a';    }    document.getElementById('here').innerHTML = content;}
var newDiv = "<div></div>";document.getElementById('here').innerHTML = newDiv;//var newElement = document.createElement('div');document.getElementById('here').appendChild(newElement);
var newElement1 = document.createElement('div');var newElementN = newElement1.cloneNode(true);
//因为每次增加div后,长度添加,此循环是死循环var divList= document.getElementByName('div');for(var count = 0; count < divList.length; count++){    document.body.appendChild(document.createElement('div'));}//集合保存到变量,避免重复查询for(var count = 0; count < document.getElementByName('div').length; count++){    //do something}var divList= document.getElementByName('div');var len = divList.lengthvar arr = toArray(divList); //toArray是自己设置的函数,将集合转成arrayfor(var count = 0; count < len; count++){    //do something}

注意:此方法会额外添加一次遍历操作,长度小的集合可能不会提升反而下降

for(var i = 0; i < document.getElementsByTagName("a").length; i++){        document.getElementsByTagName("a")[i].class = 'active'}//改进后var list = document.getElementsByTagName("a");var len = list.length;for(var i = 0; i < len; i++){        list[i].class = 'active'}
元素节点属性名被替代的属性名
childrenchildNodes
childElementCountchildNodes.length
firstElementChildfirstChild
lastElementChildlastChild
nextElementSiblingnextSibling
previousElementSiblingpreviousSibling
//改进前var els = document.getElementsById("menu").getElementsByTagName("a");//改进后var els = document.querySelectorAll("#menu a");

假如是组合查询,querySelectorAll()方法更具优势。比照一下:

//改进前var els = [];var divs = document.getElementsByTagName("div");var className = "";for(var i = 0,len = divs.length; i<len;i++){    className = divs[i].className;    if(className === 'warning' || className === 'notice'){        els.push(divs[i]);    }}//改进后var els = document.querySelectorAll("div.warning, div.notice");

推荐使用querySelector()方法,查询第一个匹配的节点。

重绘与重排

浏览器下载完所有的组件文件(html、js、css、图片)之后,会解析这些组件,并生成两个数据结构:

  1. DOM树:表示页面结构
  2. 渲染树:表示DOM节点如何显示
    DOM树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的DOM元素在渲染树中没有对应的节点)。渲染树中的节点被称为“帧”或者者“盒”,具备内边距padding,外边距margin,边框border和位置position(IE盒模型的高度和宽度包括边框和内边距,W3C只是内容部分。W3C盒模型可使用box-sizing:border-box改成IE盒模型)。一旦DOM树和渲染树构建完成,浏览器就开始显示(绘制)页面元素。
    当DOM的变化影响了元素的几何属性(宽和高),浏览器需要重新计算元素的集合属性,同事其余元素的集合属性和位置也会受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程被称为“重排reflow”。完成重排后,浏览器会重新绘制受影响的部分到平路中,该过程被称为“重绘repaint”。
    并不是所有的DOM变化都会影响几何属性,比方改变背景色,此时只会执行重绘而不会触发重排。重绘和重排都是代价昂贵的操作,需要尽量避免。

触发重排的操作

  1. 增加和删除可见的DOM元素
  2. 元素位置改变
  3. 元素尺寸变化
  4. 内容改变
  5. 页面渲染器初始化
  6. 浏览器窗口尺寸改变
  7. 滚动条的出现和消失会触发整个页面的重排

渲染树变化的排队与刷新

因为重排消耗大,大多数浏览器都会通过队列化修改并批量执行来优化重排过程。获取布局的如下操作会导致队列刷新:

最小化重排和重绘

为减少重排或者者重绘,应该合并屡次对DOM和样式的修改,而后一次性解决。

修改样式

//优化前,执行了三次重排。大部分现代浏览器进行了优化,可能只执行一次var el = document.getElementById('mydiv');el.style.borderLeft='1px';el.style.borderRight='2px';el.style.padding='5px';//优化后,只执行一次var el = document.getElementById('mydiv');el.style.ccsText='border-left:1px;border-right:2px;padding:5px;';//第二种优化方法var el = document.getElementById('mydiv');el.className='active';

批量修改DOM

可以通过如下步骤减少重绘和重排次数:

  1. 使元素脱离文档流
  2. 对其应用多重改变
  3. 把元素待会文档中
    这样操作后只会在1和3补执行两次重排,忽略了步骤2中可能的N次重排。
    使元素脱离文档流的方法有如下三种:
  4. 隐藏元素,应用修改,重新显示
  5. 使用文档片段在当前DOM之外构建一个子树,执行完修改后再把它拷贝回文档
  6. 讲原始元素拷贝到一个脱离文档的节点中,修改这个副本,完成后再替换原始元素。
function appendDataToElement(appendToElement, data) {    var a, li;    for (var i = 0, max = data.length; i < max; i++) {        a = document.createElement('a');        a.href = data[i].url;        a.appendChild(document.createTextNode(data[i].name));        li = document.createElement('li');        li.appendChild(a);        appendToElement.appendChild(li);    }};//优化前,循环内N次重排var ul = document.getElementById('mylist');appendDataToElement(ul, data);//第一种,异常和显示var ul = document.getElementById('mylist');ul.style.display = 'none';appendDataToElement(ul, data);ul.style.display = 'block';//第二种,文档片段var fragment = document.createDocumentFragment();appendDataToElement(fragment, data);document.getElementById('mylist').appendChild(fragment);//第三种,元素替换var old = document.getElementById('mylist');var clone = old.cloneNode(true);appendDataToElement(clone, data);old.parentNode.replaceChild(clone, old);

推荐第二种方案,其产生的DOM遍历和重排次数最少。

缓存布局信息

当查询布局信息(例如offsets,scroll,client等),浏览器为返回最新值,会刷新队列并应用所有变更。所哟尽量减少布局信息的获取次数,获取后赋值给局部变量,而后再操作局部变量。
例如移动元素的例子,timeout循环部分

//改进前myElement.style.left = 1 + myElement.offsetLeft + 'px';myElement.style.top = 1 + myElement.offsetTop + 'px';if (myElement.offsetLeft >= 500) {    stopAnimation();}//改进后,先一次性获取初始位置var current = myElement.offsetLeft;//而后循环执行操作current++myElement.style.left = current + 'px';myElement.style.top = current + 'px';if (current >= 500) {    stopAnimation();}

让元素脱离动画流

采用绝对位置定位,可以减少元素尺寸变化时,对其余元素造成的重排影响。
例如折叠/开展这种交互方式,每次变化都会导致下方所有元素的移动。假如把这部分元素使用绝对位置定位,覆盖其余部分。这样就能避免下方元素的重排和重绘,减少开销。

IE和:hover

从IE7开始,IE允许任何元素上使用:hover这个CSS伪选择器。假如大量使用:hover,响应速度下降显著。特别是IE8。

事件委托

假如进行大量的DOM元素事件绑定,会引入性能问题。一个简单的处理方案是事件委托。只要给最外层的元素绑定事件,利用事件逐层冒泡并能被父级元素捕获,即可以解决所有子元素上触发的事件。

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
package.json文件 -- JavaScript 标准参考教程
Linux服务器开启ssh服务,实现ssh远程登陆!
EOSPark HTTPS API 获取 EOS 账户来往交易 - EOS 区块链开发实战
通过JS加密文本的web前台三种常见方式,你使用过几个?
使用Java8新特性parallelStream遇到的坑
首页
搜索
订单
购物车
我的