高性能javascript(记录三)
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(记录三)的更多相关文章
- 高性能JavaScript笔记三(编程实践)
避免双重求值 有四个标准函数可以允许你传入代码的字符串,然后它才你动态执行.它们分别是:eval.Function.setTimeout.setInterval 事实上当你在javascript代码中 ...
- JavaScript学习记录三
title: JavaScript学习记录三 toc: true date: 2018-09-14 23:51:22 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...
- 《高性能Javascript》 Summary(三)
第八章.编程实践 Programming Practices 经验: 避免使用 eval_r()和Function构造器避免二次评估.此外,给setTimeout()和setInterval()函数传 ...
- 《高性能javascript》一书要点和延伸(上)
前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的 ...
- 《高性能javascript》学习总结
本文是学习<高性能javascript>(Nichols C. Zakes著)的一些总结,虽然书比较过时,里面的知识点也有很多用不上了,但是毕竟是前人一步步探索过来的,记录着javascr ...
- 《高性能javascript》阅读摘要
最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践.性能优化类的书.记录下主要知识. 加载和执行 脚本位置 放在<head>中的javasc ...
- 《高性能javascript》 领悟随笔之-------DOM编程篇(二)
<高性能javascript> 领悟随笔之-------DOM编程篇二 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整 ...
- 高性能javascript学习笔记系列(6) -ajax
参考 高性能javascript javascript高级程序设计 ajax基础 ajax技术的核心是XMLHttpRequest对象(XHR),通过XHR我们就可以实现无需刷新页面就能从服务器端读 ...
- 高性能JavaScript 重排与重绘
先回顾下前文高性能JavaScript DOM编程,主要提了两点优化,一是尽量减少DOM的访问,而把运算放在ECMAScript这一端,二是尽量缓存局部变量,比如length等等,最后介绍了两个新的A ...
- 高性能JavaScript(您值得一看)
众所周知浏览器是使用单进程处理UI更新和JavaScript运行等多个任务的,而同一时间只能有一个任务被执行,如此说来,JavaScript运行了多长时间就意味着用户得等待浏览器响应需要花多久时间. ...
随机推荐
- EXCEL表格实现万位分隔符效果!
单击单元格右键 选择自定义单元格格式 选择数字标签 选择自定义 在输入框中输入:###","#### 单击确定即可! 格式刷可以对其他单元格实行同样效果!
- JS函数 计算 今日,昨日,本周,上周,本月
最近有个功能会进行数据的筛选于是便写了几个快速计算 今日,昨日,本周,上周,本月 范围的function 以便以后遇到同样的问题可以直接进行复用,代码如下: /* *获取今日的起始和结束时间 *返回值 ...
- ES6 笔记
1.箭头函数 箭头函数里的this会引用 定义 箭头函数时,外部作用域 的 this .箭头函数只是 引用 外部作用域的 this ,本身不存在 this.同时因为没有 this ,所以 无法用new ...
- 单据UI界面设计开发
1.新建单据界面数据 2.创建数据模型,包括单据.单据行.设置应用缺省特性,每个模型树下只有一个红色项 3.新增动作,系统默认没有弃审支作按钮事件 4. 新增UI Form,选择对应的模型树及表单类别 ...
- An error occurred while collecting items to be installed
安装的插件:Activiti 在Eclipse安装插件时,报以下错误: An error occurred while collecting items to be installed session ...
- HBase的Write Ahead Log (WAL) —— API与基本概念
HBase的数据写入操作,会先记录到HLog中,再真正写入到MemStore中.前者是对写入友好的格式,后者是对查询友好的格式.所以前者吞吐量更高,写入成功率大,提高了系统的可靠性,“基本”可以实现宕 ...
- angular学习input输入框筛选
学习angular,看到 angular-phonecat测试用例,照着教程运行了一遍,对于初学者有点不是很理解angular 帅选代码的意思,于是找教材,参考资料,明白了input筛选原来这么简单. ...
- angularJS中的ui-router和ng-grid模块
关于angular的教程,学习了一下angular的ui-router和ng-grid这两个模块,顺便模仿着做了一个小小的东西. 代码已经上传到github上,地址在这里https://github. ...
- MySQL wamp密码修改
WAMP安装好后,mysql密码是为空的,那么要如何修改呢?其实很简单,通过几条指令就行了,下面我就一步步来操作. 首先,通过WAMP打开mysql控制台. 提示输入密码,因为现在是空,所以直接按回车 ...
- 命令行导入mysql数据
找到mysql安装目录(bin) 进入mysql mysql -u root -p 123 选中数据库 use 数据库名 导入sql source sql数据库路径