DOM(文档对象模型)是一个独立的语言,用于操作XML和HTML文档的程序接口(API)。在游览器中,主要用来与HTML文档打交道,同样也用在Web程序中获取XML文档,并使用DOM API用来访问文档中的数据。尽管DOM是个与语言无关的API,它在游览器中的接口却是用Javascript实现的。客户端脚本编程大多数的是在和底层文档打交道。

  DOM的访问和修改是有代价的。打个比方:DOM和js各自为一个岛屿,之间仅有一个收费桥梁连接。每次js访问DOM的时候就相当于途径一次这座桥,并交纳过桥费。访问的次数多了,费用也就高了。这是DOM的访问,而修改的代价就更高了,因为修改DOM会导致游览器重新计算页面的几何变化。最坏的情况就是:在循环中访问或修改元素,尤其是对HTML元素的集合循环操作。

//较慢
function innerHTMLLoop(){
for(var count = 0;count < 15000;count++){
document.getElmentById("id").innerHTML += str;
}
}
//这种方式问题在于每次循环迭代时,该元素都被访问两次:一次读取innerHTML属性值,另一次重写它。
//较快
function innerHTMLLoop2(){
var content = ' ';
for(var count = 0;count < 15000;count++){
countent += str;
}
document.getElementById("id").innerHTML += content;
}

  通用法则:减少访问DOM的次数,把运算尽量留在ECMAScript这一端处理。  

  问题:修改页面区域是用innerHTML属性还是document.createElement()的原生DOM方法好?

答案:相差无几,除开最新版的WebKit内核之外的所有游览器中,innerHTML会更快一些。如果在一个对性能有着苛刻要求的操作中更新一大段HTML,推荐使用innerHTML,因为它在绝大部分游览器中都运行的更快。但大多数日常生活操作而言,并没有太大区别,故应该根据可读性、稳定性、团队习惯、代码风格来综合决定使用哪种方式。

  节点克隆:使用DOM方法更新页面内容的另一个途径是克隆已有的元素,而不是创建新元素--换句话说也就是用element.cloneNode()代替document.createElement()。在大多数游览器,节点克隆都更有效率,但也非明显。

  HTML集合:HTML集合是包含了DOM节点引用的类数组对象。

  以下方法的返回值就是集合:document.getElementsByName() 、document.getElementsByClassName()、document.getElementsByTagName();

  以下属性同样返回HTML集合:document.images、document.links、document.forms、document.forms[0].elements;

  以上的方法和属性返回值都是HTML集合对象,这是个类数组的列表(并非真正的数组,因为没有push()和slice()之类的方法),但提供了一个类似数组的length属性,并且还能以数字索引的方式访问列表中的元素。DOM标准中定义:HTML集以一种“假死实时态”实时存在,意味着当底层文档对象更新时,它也自动更新。事实上,HTML集合一直和文档保持连接,每次当你需要最新消息时,都会重复执行查询的过程,哪怕只是获取集合的元素个数也是,因此导致性能下降。

//一个意外的死循环
var allDivs = document.getElementsByTagName("div");
for(var i = 0;i < allDivs.length;i++){
document.body.appendChild(document.createElement("div"))
}

  这就是因为html集会自动更新导致的一个死循环allDivs.length反应的是底层文档的当前状态,会随着迭代增加。

//读取一个集合的length比读取一个普通数组的length要慢得多,因为每次都要查询。

function toArray(coll){
for ( var i = 0,a = [],len = coll.length ;i<len;i++){
a[i] = coll[i];
}
return a;
}
var coll = document.getElementsByTagName("div");
var arr = toArray(coll); //比较下面两个函数:
//较慢
function loopCollection(){
for(var count = 0;count < coll.length;count++){
//代码处理
}
}
//读取元素集合的length属性会引发集合进行更新,从而提高性能消耗。优化:把集合长度缓存到一个局部变量中,然后在循环的条件退出语句中使用该变量。性能跟loopCoiedArray()一样。
//较快
function loopCopiedArray(){
for(var count = 0;count < arr.length;count++){
//代码处理
}
} 

  很多情况下如果只需要遍历一个相对较小的集合,缓存length就够了。因为虽然遍历数组比遍历集合快,但是也同时会带来额外的消耗,故因考虑是否值得使用数组拷贝。

                                                                                 访问集合元素时使用局部变量:最慢的版本每次都要读取全局document,优化后的版本缓存了一个集合的引用,最快的版本把当前的集合元素存储到一个变量。

//较慢
function collectionGlobal(){
var coll = document.getElementsByTagName("div"),
len = coll.length,
name = ' ';
for(var 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 collectionLocal(){
var coll = document.getElementsByTagName("div");
len = coll.length,
name = ' ';
for(var count = 0;count < len;count++){
name = coll[count].nodeName;
name = coll[count].nodeType;
name = coll[count].tagName;
}
return name;
}
//最快
function collectionNodesLocal(){
var coll = document.getElementsByTagName("div");
len = coll.length,
name = ' ',el=null;
for(var count = 0;count < len;count++){
el = coll[count]
name = el.nodeName;
name = el.nodeType;
name = el.tagName;
}
return name;
}

遍历DOM

  获取DOM元素:childNodes得到元素集合,nextSibling来获取每个相邻元素。

  以非递归方式遍历元素子节点:

function testNextSibling(){
var el = document.getElementById("mydiv"),
ch = el.firstChild,name = ' ';
do {
name = ch.nodeName;
}while ( ch = ch.nextSibbling);
return name;
}; function testChildNodes(){
var el = document.getElementById("mydiv");
ch = el.childNodes,
len = ch.length,name = ' ';
for ( var count = 0;count < len;count++){
name = ch[count].nodeName;
}
return name;
};

  nextSibling和childNode两种方法运行时间几乎相等,只有在IE里,nextSibling性能更高。

  元素节点:在某些情况下,只需访问元素节点,因此在循环中很可能需要检查返回节点的类型并过滤掉非元素节点。这些类型检查和过滤其实是不必要的DOM操作。大部分游览器提供API只返回元素节点。

  使用children替代childNodes会更快,因为集合项更少。

  选择器API:querySelectorAll()(使用CSS选择器定位节点)原生DOM方法比使用JS和DOM来遍历查找元素要快得多。

重绘和重排:

  游览器下载完页面中的所有组件:HTML标记、js、css、图片之后会解析并生成两个内部数据结构:DOM树(表示页面结构)和渲染树(表示DOM节点如何显示)。一旦完成,游览器就开始绘制页面元素。而当DOM的变化影响了元素的几何属性,游览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。这时就发生了重排和重绘。

  重排:游览器会使渲染树中受到影响的部分失效,并重新构造渲染树。

  发生时间:添加或删除可见DOM元素、元素位置改变、元素尺寸改变、内容改变、页面渲染器初始化、游览器窗口尺寸改变。

  重绘:完成重排后,游览器会重新绘制受影响的部分到屏幕中。

  最小化重排和重绘:为了减少发生次数,应该合并多次对DOM和样式的修改,然后一次处理。

  批量修改DOM:当需要对DOM元素进行一系列操作时,可以通过以下方式减少重排和重绘的次数:使元素脱离文档流、对其应用多重改变、把元素带回文档。

//更新指定节点数据的通用函数
function appendDataToElement(appendToElement,data){
var a,li;
for (var i = 0;max = data.length;i < max;i++){
a = document.createElement("a");
a.href = data.data[i].url;
a.appendChild(document.createTextNode(data[i].name));
li = document.createElement("li");
li.appendChild(a);
appendToElement.appendChild(li);
}
};
//不考虑重排问题
var ul = document.getElementById("myul");
appendDataToElement(ul,data);
//优化,使DOM脱离文档,减少重排
//方法一 隐藏元素,应用修改,重新显示
var ul = document.getElementById("myul");
ul.style.display = "none";
appendDataToElement(ul,data);
ul.style.display = "block";
//方法二 使用文档片断(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档
var fragment = document.createDocumentFragment();
appendDataToElement(fragment,data);
document.getElementById("myul").appendChild(fragment);
//方法三 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素
var old = document.getElementById("myul");
var clone = old.cloneNode(true);
appendDataToElement(clone,data);+
old.parentNode.replaceCHhild(clone,old); 注:推荐使用文档片断,因为它们所产生的DOM遍历和重排次数最少。

  还有两种情况下优化重排和重绘的方式:在获取布局信息时,缓存布局信息和处理页面动画时,让元素脱离动画流(注:若是大量使用css中:hover这个伪选择器会明显降低响应速度)。

  事件委托:

  当页面存在大量元素,而且每个都要一次或多次绑定事件处理器时,每绑定一个事件处理器都是有代价的,要么加重了页面负担(更多的代码),要么增加了运行期的执行时间。而一个简单而优雅的处理DOM事件的技术是事件委托。基于:事件逐层冒泡并能被父级元素捕获。

//例如
document.getElementById("menu").onclick = function(e){ //游览器 target
e = e || window.event;
var target = e.target || e.srcElement;
var pageid,hrefparts;
//只关心hrefs,非链接点击则退出
if ( target.nodeName !== "A" ){
return;
} //从链接中找出页面ID
hrefparts = target.href.split("/");
pageid = hrefparts[ hrefparts.length - 1 ];
pageid = pageid.replace(".html"," "); //更新页面
ajaxRequest("xhr.php?page" + id,updatePageContents); //游览器组织默认行为并取消冒泡
if (typeof e.preventDefault === "function"){
e.preventDefault();
e.stopPropagetion();
}else{
e.returnValue = false;
e.cancelBubble = true;
}
};

 

高性能javascript(记录三)的更多相关文章

  1. 高性能JavaScript笔记三(编程实践)

    避免双重求值 有四个标准函数可以允许你传入代码的字符串,然后它才你动态执行.它们分别是:eval.Function.setTimeout.setInterval 事实上当你在javascript代码中 ...

  2. JavaScript学习记录三

    title: JavaScript学习记录三 toc: true date: 2018-09-14 23:51:22 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...

  3. 《高性能Javascript》 Summary(三)

    第八章.编程实践 Programming Practices 经验: 避免使用 eval_r()和Function构造器避免二次评估.此外,给setTimeout()和setInterval()函数传 ...

  4. 《高性能javascript》一书要点和延伸(上)

    前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的 ...

  5. 《高性能javascript》学习总结

    本文是学习<高性能javascript>(Nichols C. Zakes著)的一些总结,虽然书比较过时,里面的知识点也有很多用不上了,但是毕竟是前人一步步探索过来的,记录着javascr ...

  6. 《高性能javascript》阅读摘要

    最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践.性能优化类的书.记录下主要知识. 加载和执行 脚本位置 放在<head>中的javasc ...

  7. 《高性能javascript》 领悟随笔之-------DOM编程篇(二)

    <高性能javascript> 领悟随笔之-------DOM编程篇二 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整 ...

  8. 高性能javascript学习笔记系列(6) -ajax

    参考 高性能javascript javascript高级程序设计 ajax基础  ajax技术的核心是XMLHttpRequest对象(XHR),通过XHR我们就可以实现无需刷新页面就能从服务器端读 ...

  9. 高性能JavaScript 重排与重绘

    先回顾下前文高性能JavaScript DOM编程,主要提了两点优化,一是尽量减少DOM的访问,而把运算放在ECMAScript这一端,二是尽量缓存局部变量,比如length等等,最后介绍了两个新的A ...

  10. 高性能JavaScript(您值得一看)

    众所周知浏览器是使用单进程处理UI更新和JavaScript运行等多个任务的,而同一时间只能有一个任务被执行,如此说来,JavaScript运行了多长时间就意味着用户得等待浏览器响应需要花多久时间. ...

随机推荐

  1. EXCEL表格实现万位分隔符效果!

    单击单元格右键 选择自定义单元格格式 选择数字标签 选择自定义 在输入框中输入:###","#### 单击确定即可! 格式刷可以对其他单元格实行同样效果!

  2. JS函数 计算 今日,昨日,本周,上周,本月

    最近有个功能会进行数据的筛选于是便写了几个快速计算 今日,昨日,本周,上周,本月 范围的function 以便以后遇到同样的问题可以直接进行复用,代码如下: /* *获取今日的起始和结束时间 *返回值 ...

  3. ES6 笔记

    1.箭头函数 箭头函数里的this会引用 定义 箭头函数时,外部作用域 的 this .箭头函数只是 引用 外部作用域的 this ,本身不存在 this.同时因为没有 this ,所以 无法用new ...

  4. 单据UI界面设计开发

    1.新建单据界面数据 2.创建数据模型,包括单据.单据行.设置应用缺省特性,每个模型树下只有一个红色项 3.新增动作,系统默认没有弃审支作按钮事件 4. 新增UI Form,选择对应的模型树及表单类别 ...

  5. An error occurred while collecting items to be installed

    安装的插件:Activiti 在Eclipse安装插件时,报以下错误: An error occurred while collecting items to be installed session ...

  6. HBase的Write Ahead Log (WAL) —— API与基本概念

    HBase的数据写入操作,会先记录到HLog中,再真正写入到MemStore中.前者是对写入友好的格式,后者是对查询友好的格式.所以前者吞吐量更高,写入成功率大,提高了系统的可靠性,“基本”可以实现宕 ...

  7. angular学习input输入框筛选

    学习angular,看到 angular-phonecat测试用例,照着教程运行了一遍,对于初学者有点不是很理解angular 帅选代码的意思,于是找教材,参考资料,明白了input筛选原来这么简单. ...

  8. angularJS中的ui-router和ng-grid模块

    关于angular的教程,学习了一下angular的ui-router和ng-grid这两个模块,顺便模仿着做了一个小小的东西. 代码已经上传到github上,地址在这里https://github. ...

  9. MySQL wamp密码修改

    WAMP安装好后,mysql密码是为空的,那么要如何修改呢?其实很简单,通过几条指令就行了,下面我就一步步来操作. 首先,通过WAMP打开mysql控制台. 提示输入密码,因为现在是空,所以直接按回车 ...

  10. 命令行导入mysql数据

    找到mysql安装目录(bin) 进入mysql mysql -u root -p 123 选中数据库 use 数据库名 导入sql  source sql数据库路径