高性能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;
    }
};

 

时间: 2024-11-08 07:32:05

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

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

避免双重求值 有四个标准函数可以允许你传入代码的字符串,然后它才你动态执行.它们分别是:eval.Function.setTimeout.setInterval 事实上当你在javascript代码中执行另外一段javascript代码时,都会导致双重求值的性能消耗,所以在大多数情况下,没必要使得eval和Function函数,因此最好避免使用它们.至于setTimeout和setInterval,建议传入函数而不是字符串来作为第一个参数 现在Safari4和chrome的javaScript引

【JavaScript】【译】编写高性能JavaScript

英文链接:Writing Fast, Memory-Efficient JavaScript 很多JavaScript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执行的大型JavaScript应用所设计的.如果你是一个开发者,并且关心内存使用情况与页面性能,你应该了解用户浏览器中的JavaScript引擎是如何运作的.无论是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,这样做可以帮助你更好地优

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

前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的收获. 第一章 加载和执行 基于UI单线程的逻辑,常规脚本的加载会阻塞后续页面脚本甚至DOM的加载.如下代码会报错: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8

高性能JavaScript 重排与重绘

转自(学习之用) 作者:韩子迟 网址:http://www.cnblogs.com/zichi/p/4720000.html 先回顾下前文高性能JavaScript DOM编程,主要提了两点优化,一是尽量减少DOM的访问,而把运算放在ECMAScript这一端,二是尽量缓存局部变量,比如length等等,最后介绍了两个新的API querySelector()以及querySelectorAll(),在做组合选择的时候可以大胆使用.而本文主要讲的是DOM编程可能最耗时的地方,重排和重绘. 1.什

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

参考 高性能javascript javascript高级程序设计 ajax基础  ajax技术的核心是XMLHttpRequest对象(XHR),通过XHR我们就可以实现无需刷新页面就能从服务器端读取数据 var xhr = new XMLHttpRequest(); //只支持IE7以及更高的版本 xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if( (xhr.status >= 200 && xh

高性能JavaScript(您值得一看)

众所周知浏览器是使用单进程处理UI更新和JavaScript运行等多个任务的,而同一时间只能有一个任务被执行,如此说来,JavaScript运行了多长时间就意味着用户得等待浏览器响应需要花多久时间. 从认知上来说,解析器解析一个界面的时候都是从上至下依次解析的,这就是说界面上出现多少个<script>标签(不管是内联还是外部文件),页面下载和解析必须停止等待脚本下载完成并运行完成(注意这里包括运行),这个过程当中,页面解析和用户交互是被完全阻塞的. Javascript第一条定律:将脚本放在底

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

<高性能javascript> 领悟随笔之-------DOM编程篇二 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整个页面文档.DOM编程性能一直以来都是非常受开发者关注的话题,如何编写高性能的DOM是前端开发必不可少的技能. 1.重绘与重排 当浏览器加载完页面所有的元素.js.css.图片之后会自动生成两个数据结构: 1.dom树 (图片为转载) 如图所示,dom树表示了整个页面文档的结构,通过访问dom树我们可以得到某

加载和运行---高性能JavaScript读书笔记(1)

众所周知大多数浏览器是使用单进程处理UI更新和JavaScript运行等多个任务的,而同一时间只能有一个任务被执行,如此说来,JavaScript运行了多长时间就意味着用户得等待浏览器响应需要花多久时间. 从认知上来说,解析器解析一个界面的时候都是从上至下依次解析的,这就是说界面上出现多少个<script>标签(不管是内联还是外部文件),页面下载和解析必须停止等待脚本下载完成并运行完成(注意这里包括运行),这个过程当中,页面解析和用户交互是被完全阻塞的. Javascript第一条定律:将脚本

Ext JS学习第四天 我们所熟悉的javascript(三)

此文用来记录学习笔记: •javascript之函数 •this关键字的使用 –this关键字总是指向调用者,谁调用函数,this就指向谁 •call.apply的使用 –call和apply主要应用与框架底层,用于绑定函数的执行环境/作用域 •块的概念 –和高级程序语言不同,js里没有块的概念,我们一般用小括号包裹块级作用域 •闭包:掌握闭包必须要深入清楚的概念 –执行环境 –作用域链 –垃圾回收机制 附上栗子 代码 1 // This 关键字 在javascript里的使用 2 //this

构建高性能JavaScript应用

前端性能优化准则: 一.减少http请求. 措施:合并小图片,css控制背景图.合并CSS,合并JS 二.使用CDN(Content Deliver Network 内容分发网络)发布静态资源. 三.启用压缩组件. response header:Content-encoding request header:Accept-encoding 四.添加Expires响应头. response header:Expires(指定过期时间) 缺点:1.需要指定具体的过期日期,时间到后需要重新指定.2.