html dom 优化,DOM性能优化

DOM编程

用JavaScript进行DOM操作的代价很昂贵,它是富Web应用中最常见的性能瓶颈.

文档对象模型DOM是一个独立于语言,用于操作XML和HTML文档的程序接口(API).在浏览器中主要用来和HTML文档打交道.

尽管DOM是个与语言无关的API,它在浏览器中的接口却是用JavaScript实现的.客户端脚本编程大多时候是在底层文档打交道,DOM就成为现在JavaScript编码中的重要部分

浏览器中通常会把DOM和JavaScript单独实现,JavaScript引擎和DOM渲染引擎相互对立.

这对性能来说意味着什么?简单理解,两个相互对立的功能只要通过接口就能彼此连接,就会产生消耗.把DOM和JavaScript各比作一座岛屿,它们之间用收费桥梁连接.ECMAscript每次访问DOM,都会这过座桥,并交纳过桥费,访问的DOM次数越多,费用就越高.因此推荐的做法就是尽可能的减少过桥的次数

DOM的访问与修改

访问DOM元素是有代价的, 修改元素更为昂贵,因为它会导致浏览器重新计算页面的几何变化.

为了让你对DOM编程带来的性能问题有个量化的了解,下面是简单实例:

function renderHtml(){

for(let count = 0, ; count < 1000;count++ ){

document.getElementById("box").innerHTML += a;

}

}

这个函数循环修改页面元素内容.这段代码的问题在于,每次循环迭代,该元素都被访问两次,一次读取innerHTML属性,另一次是重写它.

换一种效率更高的方法,用局部变量储存修改的内容,在循环结束后一次性写入;

function renderHtml2(){

var content = '';

for(let count = 0; count < 1000; count++){

content += 'a';

}

document.getElementById("box").innerHTML += content;

}

如果未来要多次修改和访问DOM元素, 更好的方法将访问的元素用一个局部变量或者全局变量缓存起来, 减少访问DOM次数

const box = document.getElementById("box");//定义一个变量用来存储DOM元素, 方便以后多次访问

function renderHtml2(){

var content = '';

for(let count = 0; count < 1000; count++){

content += 'a';

}

box.innerHTML += content;

}

在所有浏览器中,修改后的版本都运行得更快. 访问DOM的次数越多,代码运行的速度就越慢, 因此通用的经验法则就是:减少访问DOM的次数,

把运算尽量留在ECMAscript这一端处理.

innerHTML和DOM方法的对比

多年来,在web开发社区围绕这个问题有着许多讨论,修改页面区域的最佳方案是用非标准但支持良好的innerHTML属性呢?还是只用document.createElement的原生DOM方法? 如果不考虑web标准, 答案是相差无几. 但是,除开最新版WebKit内核(Chrome和Safari)

之外的所有浏览器中,innerHTML会更快一些.

用innerHTML生产一个ul列表

const body = document.body;

let data = ['name','age','sex','prototype','hobby','height']

function renderUl(rednerData){//参数rednerData为生成的li文本内容数据

let content =['

  • ',, '
'],

length = rednerData.length,

liList = '';

for(let i = 0; i < length; i++){

liList += '

' +rednerData[i]+ ''

};

content[1] = liList;

let ul = content.reduce( (acc,cur) => acc + cur )

body.innerHTML = ul

}

使用DOM方法生成相同的ul列表

const body = document.body;

let data = ['name','age','sex','prototype','hobby','height']

function renderUl2(rednerData){//参数rednerData为生成的li文本内容数据

let ui = document.createElement("ul");

rednerData.forEach( item =>{

let li = document.createElement("li");

li.innerText = item;

ui.append(li)

})

body.append(ui)

}

在旧版本的浏览器中,innerHTMl的优势更加明显,但在新版本浏览器中优势不那么明显. 在基于WebKit内核的新版浏览器中恰恰相反; 使用DOM方法略胜一筹,因此, 最终选择哪种方法取决于你的用户经常使用的浏览器,以及你的编码习惯;

如果对于性能没有那么苛刻的要求,一般推荐使用原生的DOM方法,更容易养成好的编码习惯,所以你更应该根据可读性,稳定性,团队习惯,代码风格来综合决定使用哪种方式

HTML集合

HTML集合是包含DOM节点引用的类数组对象.以下方法的返回值就是一个集合.

document.getElementByname();

document.getElementsByClasssName();

document.getElementsByTagName();

下面的属性同样返回HTML集合

documen.images

页面中所有的img元素

document.links

页面中所有的a元素

document.froms

页面中所有的表单元素

document.form[0].elements

页面中第一个表单的所有字段

以上方法和属性的返回值为HTML集合对象,这是个类似数组的列表.他们并不是真正的数组(因为没有push()和slice方法()之类的方法),但它提供了类似数组的length属性,并且可以通过数字索引访问列表中的元素,例如, document.images[0]返回集合中第一个元素.正如DOM标准中所定义的,HTML集以一种 假定实时态,这意味着当底层文档对象更新时,它也会知道更新

事实上,HTML集合一直与文档保持连接,每次你需要最新的信息时,都会重复执行查询的过程.哪怕只是获取集合中的元素个数(即访问集合的length属性), 也是如此,这正是昂贵性能消耗的源头.

昂贵的集合

为了演示集合的实时性,考虑以下的代码片段

//一个意外的死循坏

let allDivs = document.getElementsByTagName("div");

for(let i = 0; i < allDivs.length; i++){

let div = document.createElement("div");

document.body.appendChild(div);

}

这段代码看上去只是简单把页面的div元素数量翻倍.它遍历现有的div元素,每次创建一个新的div并添加到body中.但事实上这是一个死循环,意外循环的退出条件allDivs.length每次迭代时都会增加,它反映的是底层文档的实时当前状态.

像这样遍历HTML集合可能会导致逻辑错误,而且也很慢,因为每次迭代都会执行查询操作.

通常来说在循环的条件控制语句中读取数组的length属性是不推荐的做法.而且读取一个集合的length比读取普通数组的length属性要慢很多,因为每次都要重新查询.

上面的代码可以改下成下面的代码, 把集合的length属性用一个局部变量保存起来,避免重复的读取集合的length属性,导致死循坏

在每次迭代过程中.读取元素的集合的length属性会引发集合进行更新,这是所有浏览器中都有的明显的性能问题,优化方法很简单吗把集合的长度缓存到一个局部变量,然后在循环退出的条件语句中使用该变量

let allDivs = document.getElementsByTagName("div"),

length = allDivs.length;

for(let i = 0; i

let div = document.createElement("div");

document.body.appendChild(div);

}

在相同的内容和数量上,遍历应该数组的速度明显快于遍历一个HTML集合.

考虑一个API把一个HTML集合拷贝成一个普通的数组.

let allDivs = document.getElementsByTagName("div"),

allDivsToArr = Array.form(allDivs);// es6新api把类数组转为真正的数组

for(let i = 0,length = allDivs.length;; i

let div = document.createElement("div");

document.body.appendChild(div);

}

很多情况下如果只需要遍历一个相对较小的集合,那么缓存length就够了.但是由于变遍历数组比遍历集合快,因此如果将集合元素拷贝到数字中,那么访问它的属性就更快.请记住,这会因额为的步骤带来消耗,而且会多遍历一遍集合,因此应当在评估特定的条件下评估使用数组拷贝是否有帮助.

访问集合元素中使用局部变量

一般来说,对于任何类型的DOM访问,需要多次访问同一个DOM属性或方法或方法需要多次访问,最好使用一个局部变量缓存此成员.当遍历一个集合时,第一优先原则就是把集合缓存到局部变量中, 并把length属性缓存在循坏外部,使用局部变量替代这些需要多次读取的元素

看下面的例子,在循坏体中读取元素的三个属性.最慢的版本每次都要读取全局的document,优化的版本缓存了一个集合的引用, 最快的版本把当前集合元素缓存到一个变量,这三个版本都缓存了集合的length属性.

//较慢

function collectionColbal(){

let coll = document.getElementsByTagName('div'),

len = coll.length,

name = '';

for(let count = 0; count < len; count++){

name = document.getElementsByTagName('div')[count].nodeName;

name = document.getElementsByTagName('div')[count].nodeType;

name = document.getElementsByTagName('div')[count].tagName;

}

return name;

}

//较快

function collectionColbal(){

let coll = document.getElementsByTagName('div'),

len = coll.length,

name = '';

for(let count = 0; count < len; count++){

name = coll[count].nodeName;

name = coll[count].nodeType;

name = coll[count].tagName;

}

return name;

}

//最快

function collectionColbal(){

let coll = document.getElementsByTagName('div'),

len = coll.length,

name = '',

el = null;

for(let count = 0; count < len; count++){

el = coll[count]//把集合元素缓存到一个变量

name = el.nodeName;

name = el.nodeType;

name = el.tagName;

}

return name;

}

在遍历DOM读取属性时,使用局部变量存储集合引用和集合元素可以带来显著的性能提升.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值