首頁  >  文章  >  web前端  >  JavaScript實踐效能

JavaScript實踐效能

伊谢尔伦
伊谢尔伦原創
2017-02-03 16:21:181122瀏覽

一.注意作用域

避免全局查找

一個例子:

function updateUI(){
    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.innnerHTML = "Update complete.";
}

該函數可能看上去完全正常,但是它包含了三個對於全局document對象的引用。如果在頁面上有多個圖片,那麼for迴圈中的document引用就會被執行多次甚至上百次,每次都會要進行作用域鏈查找。透過建立一個指向document物件的局部變量,就可以透過限制一次全域查找來改進這個函數的效能:

function updateUI(){
    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.innnerHTML = "Update complete.";
}

這裡,首先將document物件存在本地的doc變數中;然後在餘下的程式碼中取代原來的document 。與原來的版本相比,現在的函數只有一次全域查找,肯定更快。

二. 選擇正確方法

1.避免不必要的屬性查找

獲取常數值是非常高效的過程

var value = 5;
var sum = 10 + value;
alert(sum);

該代碼進行了四次常數值查找:數字5,變量value,數字10和變量sum。

在JavaScript中存取陣列元素和簡單的變數查找效率一樣。所以以下程式碼和前面的範例效率一樣:

var value = [5,10];
var sum = value[0] + value[1];
alert(sum);

物件上的任何屬性查找都比存取變數或陣列花費更長時間,因為必須在原型鏈中對擁有該名稱的屬性進行一次搜素。屬性查找越多,執行時間越長。

var values = {first: 5, second: 10};
var sum = values.first + values.second;
alert(sum);

這段程式碼使用兩次屬性查找來計算sum的值。進行一兩次屬性查找並不會導致顯著的效能問題,但是進行數百次則肯定會減慢執行速度。

注意取得單一值的多重屬性查找。例如:

var query = window.location.href.substring(window.location.href.indexOf("?"));

在這段程式碼中,有6次屬性查找:window.location.href.substring()有3次,window.location.href.indexOf()又有3次。只要數一數代碼中的點的數量,就可以確定查找的次數了。這段程式碼由於兩次用到了window.location.href,同樣的查找進行了兩次,因此效率特別不好。

一旦多次使用到物件屬性,應該將其儲存在局部變數中。先前的程式碼可以如下重寫:

var url = window.locaiton.href;
var query = url.substring(url.indexOf("?"));

這個版本的程式碼只有4次屬性查找,相對於原始版本節省了33%。

一般來講,只要能減少演算法的複雜度,就要盡量減少。盡可能多地使用局部變數將屬性查找替換為值查找,進一步獎,如果即可以用數字化的數組位置進行訪問,也可以使用命名屬性(諸如NodeList物件),那麼使用數字位置。

2.最佳化循環

一個循環的基本最佳化步驟如下圖所示。

(1)減損迭代-大多數迴圈使用一個從0開始、增加到某個特定值的迭代器。在很多情況下,從最大值開始,在循環中不斷減值的迭代器更有效率。

(2)簡化終止條件-由於每次循環過程都會計算終止條件,所以必須確保它盡可能快。也就是說避免屬性查找或其他操作。

(3)簡化循環體-循環是執行最多的,所以要確保其最大限度地優化,確保其他某些可以被很容易移除循環的密集計算。

(4使用後測循環-最常用for循環和while循環都是前測循環。而如do-while這種後測循環,可以避免最初終止條件的計算,因此運行更快。

以下是一個基本的for迴圈:

for(var i=0; i < value.length; i++){
    process(values[i]);
}

這段程式碼中變數i從0遞增到values陣列中的元素總數。了0。住使用「後測」循環時必須確保要處理的值至少有一個,空數組會導致多餘的一次循環而「前測」循環則可以避免。的,消除迴圈並使用多次函數呼叫往往更快。運行更快。一系列語句。

var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;

if(leftover>0){
    do{
        process(values[i++]);
    }while(--leftover > 0);
}
do{
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
}while(--iterations > 0);

在这个实现中,剩余的计算部分不会在实际循环中处理,而是在一个初始化循环中进行除以8的操作。当处理掉了额外的元素,继续执行每次调用8次process()的主循环。

针对大数据集使用展开循环可以节省很多时间,但对于小数据集,额外的开销则可能得不偿失。它是要花更多的代码来完成同样的任务,如果处理的不是大数据集,一般来说不值得。

4.避免双重解释

当JavaScript代码想解析KavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。

//某些代码求值——避免!!
eval("alert(&#39;Hello world!&#39;)");

//创建新函数——避免!!
var sayHi = new Function("alert(&#39;Hello world!&#39;)");

//设置超时——避免!!
setTimeout("alert(&#39;Hello world!&#39;)", 500);

在以上这些例子中,都要解析包含了JavaScript代码的字符串。这个操作是不能在初始的解析过程中完成的,因为代码是包含在字符串中的,也就是说在JavaScript代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码要比直接解析慢得多。

//已修正
alert(&#39;Hello world!&#39;);

//创建新函数——已修正
var sayHi = function(){
    alert(&#39;Hello world!&#39;);
};

//设置一个超时——已修正
setTimeout(function(){
    alert(&#39;Hello world!&#39;);
}, 500);

如果要提高代码性能,尽可能避免出现需要按照JavaScript解析的字符串。

5.性能的其他注意事项

(1)原生方法较快

(2)Switch语句较快

(3)位运算符较快

三. 最小化语句数

1.多个变量声明

//4个语句——很浪费
var count = 5;
var color = "blue";
var values = [1,2,3];
var now = new Date();

//一个语句
var count = 5,
    color = "blue",
    values = [1,2,3],
    now = new Date();

2.插入迭代值

当使用迭代值的时候,尽可能合并语句。

var name = values[i];
i++;

前面这2句语句各只有一个目的:第一个从values数组中获取值,然后存储在name中;第二个给变量i增加1.这两句可以通过迭代值插入第一个语句组合成一个语句。

var name = values[i++];

3.使用数组和对象字面量

//用4个语句创建和初始化数组——浪费
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;

//用4个语句创建和初始化对象——浪费
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function(){
    alert(this.name);
};

这段代码中,只创建和初始化了一个数组和一个对象。各用了4个语句:一个调用构造函数,其他3个分配数据。其实可以很容易地转换成使用字面量的形式。

//只有一条语句创建和初始化数组
var values = [13,456,789];

//只有一条语句创建和初始化对象
var person = {
    name : "Nicholas",
    age : 29,
    sayName : function(){
        alert(this.name);
    }
};

重写后的代码只包含两条语句,减少了75%的语句量,在包含成千上万行JavaScript的代码库中,这些优化的价值更大。
只要有可能,尽量使用数组和对象的字面量表达方式来消除不必要的语句。

四 .优化DOM交互

1.最小化现场更新

一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。现场更新进行得越多,代码完成执行所花的事件就越长。

var list = document.getElementById(&#39;myList&#39;),
    item,
    i;
for (var i = 0; i < 10; i++) {
    item = document.createElement("li");
    list.appendChild(item);
    item.appendChild(document.createTextNode("Item" + i));
}

这段代码为列表添加了10个项目。添加每个项目时,都有2个现场更新:一个添加li元素,另一个给它添加文本节点。这样添加10个项目,这个操作总共要完成20个现场更新。

var list = document.getElementById(&#39;myList&#39;),
    fragment = document.createDocumentFragment(),
    item,
    i;
for (var i = 0; i < 10; i++) {
    item = document.createElement("li");
    fragment.appendChild(item);
    item.appendChild(document.createTextNode("Item" + i));
}
list.appendChild(fragment);

在这个例子中只有一次现场更新,它发生在所有项目都创建好之后。文档片段用作一个临时的占位符,放置新创建的项目。当给appendChild()传入文档片段时,只有片段中的子节点被添加到目标,片段本身不会被添加的。

一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。

2.使用innerHTML

有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。

当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。

var list = document.getElementById("myList");
    html = "";
    i;
for (i=0; i < 10; i++){
    html += "<li>Item " + i +"</li>";
}
list.innerHTML = html;

使用innerHTML的关键在于(和其他的DOM操作一样)最小化调用它的次数。

var list = document.getElementById("myList");
    i;
for (i=0; i < 10; i++){
    list.innerHTML += "<li>Item " + i +"</li>";  //避免!!!
}

这段代码的问题在于每次循环都要调用innerHTML,这是极其低效的。调用innerHTML实际上就是一次现场更新。构建好一个字符串然后一次性调用innerHTML要比调用innerHTML多次快得多。

3.使用事件代理(根据第13章的概念,我认为此处应为“事件委托”更为妥当)

4.注意HTMLCollection

任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。

var images = document.getElementsByTagName("img"),
    image,
    i,len;
for (i=0, len=images.length; i < len; i++){
    image = images[i];
    //处理
}

将length和当前引用的images[i]存入变量,这样就可以最小化对他们的访问。发生以下情况时会返回HTMLCollection对象:

  • 进行了对getElementsByTagName()的调用;

  • 获取了元素的childNodes属性;

  • 获取了元素的attributes属性;

  • 访问了特殊的集合,如document.forms、document.images等。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn