书写高质量的代码之性能

  1. 注意作用域
    • 避免全局查找 访问全局变量总是要比访问局部变量要慢,应为需要遍历作用域链,如下例所示: [php]function update(){ var imgs=document.getElementByTagName("img"); for(var i=0,len=imgs.length;i<len;i++){ imgs[i].title=document.title+"image+i; } var msg=document.getElementById("msg"); msg.innerHTML="Update complete"; } [/php] 这个函数包含了三个队全局变量的引用,如果页面上有许多图片,那么for循环中对document的引用就会被执行多次甚至上百次,每次都会要进行作用于链查找,这对性能是极大的消耗。通过创建一个指向document对象的局部变量就可以解决这一问题,如下所示: [php] function update(){ var doc=document; var imgs=doc.getElementByTagName("img"); for(var i=0,len=imgs.length;i<len;i++){ imgs[i].title=doc.title+"image+i; } var msg=doc.getElementById("msg"); msg.innerHTML="Update complete"; } [/php] 将document对象存至本地后,现在的函数只有一次全局查找,性能就提升了。
    • 避免with语句 和函数类似,with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。需要用with语句的情况很少,它主要用于消除额外的字符,大多数情况下可以使用局部变量完成相同的事情而不引入新的作用域。如下所示: [php] function updateBody(){ with(document.body){ alert(tagName); innerHTML="hello world!"; } } [/php] 这段代码with语句创建了一个document.body的内部作用域,使的操作更方便,用局部变量替代方法如下: [php] function updateBody(){ var body=document.body; alert(body.tagName); body.innerHTML="hello,world!"; } [/php]
  2. 选择正确的方法
    • 避免不必要的属性查找 减少查找的次数,降低程序的复杂度,也可以很大的提升性能,如下所示 [php] var query=window.location.href.substring(window.location.href.indexOf("?"); [/php] 这句代码有6次属性查找,括号内外各3次,重写优化如下: [php]var url=window.location.href; var query=url.substring(url.indexOf("?")); [/php] 这时候就只有4次属性查找了,相对原是版本提升了33%的性能。 一般来说,尽可能的使用局部变量将属性查找替换为值查找,如果可以使用数字化的数组位置进行访问,也可以使用命名属性,那么优先使用数字位置。书上还介绍了程序复杂度的一套计算方式,在此就不记录了。
    • 优化循环 优化循环的基本步骤如下
      1. 减值迭代——大多数情况下我们习惯于从0开始,实际上,从最大值开始递减更高效;
      2. 简化终止操作——每次循环都会计算终止条件,尽量保持它简单;
      3. 简化循环体——循环体是执行的最多的,随意要确保其被最大化的优化;
      4. 使用后测试循环——for和while都是前测试循环,而想do-while这种后测试循环,可以避免最初终止提哦啊见的计算,因此更快
      如下面一个基本的for循环 [php] for(var i=0;i<values.length-1;i++){ process(values[i]); } [/php] 第一步优化,减值迭代,如下 [php] for(var i=values.length-1;i>=0;i--){ process(values[i]); } [/php] 这样每次比较的终止条件就是常量0了,而不是带获取的values.length;进一步优化,改为后测试循环,如下所示 [php] var i=values.length-1; if(i>-1){ do{ process(values[i]); }while(--i>=0); } [/php]
    • 展开循环 当循环次数是确定的,消除循环并使用多次函数调用往往更快,比如前面的例子,假设values数组里面之用3个元素,则像下面这样直接调用precess()更快: [php] process(values[0]); process(values[1]); process(values[2]); [/php] 这样就避免了建立循环和处理终止条件带来的额外开销了。那么如果循环中的迭代次数未知呢?那可以考虑一种叫做Duff装置的技术。Duff技术的基本概念是通过计算迭代的次数是否为8的倍数来讲一个循环展开为一些类的语句,如下所示 [php] var iterations=Math.ceil(values.length/8); var startAt=value.length%8; var i=0; do{ switch(startAt){ case0:process(values[i++]); case7:process(values[i++]); case6:process(values[i++]); . . . case1:process(values[i++]); } startAt=0; }while(--iterations>0); [/php] 这种技术还是第一次听说,印象很深刻。当然,还可以进一步优化这种技术,将do-while循环分为2个单独的循环。 如下所示: [php] var iterations=Math.ceil(values.length/8); var startAt=value.length%8; var i=0; if(startAt>0){ do{ process(values[i++]; }while(--stateAt>0); do{ case0:process(values[i++]); case7:process(values[i++]); case6:process(values[i++]); . . . case1:process(values[i++]); }while(--iterations>0); [/php] [/php] 针对大数据集使用展开循环可以节省很多的时间,但对于小数据集,额外的开销则可能得不偿失。所以,如果并非是大数据,没必要采用这种方法。但我们可以先学着嘛,谁没有一颗怀着操作大数据的愿望的心呢?
    • 避免双重解释 这个很好理解,既不用要直接将字符串参数传递给eval()、function()构造函数以及setTimeout()等函数,而是封装在函数中传递给它们,如下所示: [php] //不推荐 eval("alert('hello,world')); var sayHi=new Function("alert('hello,world')); setTimeout("alert('hello,world')",500); //推荐 alert('hello,world'); var sayHi=function(){ alert('hello,world'); setTimeout(function(){ alert('hello,world'); }500); [/php]
    • 其他注意事项
      1. 原生方法更快——尽量使用原生方法而不是自己写一个javascript函数,比较容易忽视的是Math对象中的各种复杂的数学运算
      2. Switch语句较快——如果有一系列的if-else语句,可以转换成单个的switch语句,还可以通过将case语句按最可能出现的到最不可能出现的顺序进行组织,进一步优化switch语句
      3. 位运算符更快——进行数学运算时,位运算操作比任何布尔值或者算数都快,在取模,逻辑与和逻辑或时都可以考虑用位运算来替换。
  3. 最小化语句数
    • 多个变量声明 这个很容易理解,不要使用var一个个定义变量,而是一次定义多个变量,这样书写也要快些。
    • 插入迭代值 使用迭代值的时候,尽可能合并语句,如下: [php] var name=values[i]; i++; //换为 var name=values[i++]; [/php]
    • 使用数组和对象字面量 这个也很好理解,如下例所示 [php] var values=new Array(); values[0]=1; values[1]=2; values[2]=3; //替换为数组如下 var values=[1,2,3]; var person=new Object(); person.name=HUSTecho; person.age=21; //替换为 var person={ name:HUSTecho; age:21; } [/php]
  4. 优化dom操作
    • 最小化现场更新 一旦你需要访问的dom部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。现场更新进行的越多,代码完成执行所花的时间就越长;完成一个更新所需的县城越少,代码就越快。如下所示: [php] var list=document.getElementById("mylist"), item, i; for(i=0;i<10;i++){ item=document.creatElement("li"); list.appendChild(item); item.appendChild(document.creatTextNode("Item"+i)); } [/php] 这段代码在每次添加项目是都由于两个现场同时更新:一个添加
    • 元素,一个添加文本节点。可以通过文档碎片来构建DOM结构,如下所示: [php] var list=document.getElementById("mylist"), fragment=document.createDocumentFragement(), item, i; for(i=0;i<10;i++){ item=document.createElement("li"); fragement.appendChild(item); item.appendChild(document.creatTextNode("item"+i); } list.appendChild(fragement); [/php] 这样通过创建一个文档碎片作为临时的占位符,放置新建的项目,然后使用appendChild()一次性将所有的项目添加到列表中。 记住:一旦需要更新DOM,首先考虑使用文档碎片来构建DOM结构,然后在将其添加到现存的文档中。
    • 使用innerHTML 有两种方法在页面上创建DOM节点,一种是使用像createElement()和appendChild()这样的DOM方法,另一种是使用innerHTML().在更改小DOM时,两种方法小效率差不多,但在大DOM更改时,innerHTML要比标准的DOM方法快得多。例如前面的例子使用innerHTML改写如下: [php] var list=document.getElementById("mylist"), html, i; for(i=0;i<10;I++){ html+="</pre> </li> <li>Item:"+i+"</li> <li> <pre>"; } list.innerHTML(html); [/php] 其实innerHTML和最小化现场的思想是一样,都可以这样想:先把数据集中到一起,然后依次性操作,避免频繁操作DOM
    • 使用事件代理 这个已经提到过几次了,基于冒泡机制直接在祖先元素下操作。
    • 注意HTMLcollection 这个在前面也讨论过了,如前面说到的全局对象本地化,就是这种类型。记住一下情况会返回HTMLCollection对象:
      • 进行了对getElementByTagName()的调用;
      • 获取了元素的childNodes()属性;
      • 获取了元素的attribute属性;
      • 获取了特殊的集合,如document.froms、document.images等
下一期是书写高质量的代码之部署,慢慢梳理。
posted @ 2013-04-01 14:15  echoHUST  阅读(178)  评论(0编辑  收藏  举报