前端性能優化(JavaScript篇)

正巧看到在送書,因而乎找了找本身博客上記錄過的一些東西來及其無恥的蹭書了~~~javascript

小廣告:更多內容能夠看個人博客html

優化循環

若是如今有個一個data[]數組,須要對其進行遍歷,應當怎麼作?最簡單的代碼是:前端

for (var i = 0; i < data.length; i++) {
    //do someting
}

這裏每次循環開始前都須要判斷i是否小於data.length,JavaScript並不會對data.length進行緩存,而是每次比較都會進行一次取值。如咱們所知,JavaScript數組實際上是一個對象,裏面有個length屬性,因此這裏實際上就是取得對象的屬性。若是直接使用變量的話就會少一次索引對象,若是數組的元素不少,效率提高仍是很可觀的。因此咱們一般將代碼改爲以下所示:java

for(var i = 0, m = data.length; i < m; i++) {
    //do someting
}

這裏多加了一個變量m用於存放data.length屬性,這樣就能夠在每次循環時,減小一次索引對象,可是代價是增長了一個變量的空間,若是遍歷不要求順序,咱們甚至能夠不用m這個變量存儲長度,在不要求順序的時候可使用以下代碼:web

for(var i = data.length; i--; ) {
    //do someting
}

固然咱們可使用while來替代:正則表達式

var i = data.length;
while(i--) {
    //do someting
}

這樣就可只使用一個變量了算法

運算結果緩存

因爲JavaScript中的函數也是對象(JavaScript中一切都是對象),因此咱們能夠給函數添加任意的屬性。這也就爲咱們提供符合備忘錄模式的緩存運算結果的功能,好比咱們有一個須要大量運算才能得出結果的函數以下:數組

function calculator(params) {
    //大量的耗時的計算 
    return result;
}

若是其中不涉及隨機,參數同樣時所返回的結果一致,咱們就能夠將運算結果進行緩存從而避免重複的計算:瀏覽器

function calculator(params) {
    var cacheKey = JSON.stringify(params);
    var cache = calculator.cache = calculator.cache || {};
    if(typeof cache[cacheKey] !== 'undefined') {
        return cache[cacheKey];
    }
    //大量耗時的計算
    cache[cacheKey] = result;
    return result;
}

這裏將參數轉化爲JSON字符串做爲key,若是這個參數已經被計算過,那麼就直接返回,不然進行計算。計算完畢後再添加入cache中,若是須要,能夠直接查看cache的內容:calculator.cache緩存

這是一種典型的空間換時間的方式,因爲瀏覽器的頁面存活時間通常不會很長,佔用的內存會很快被釋放(固然也有例外,好比一些WEB應用),因此能夠經過這種空間換時間的方式來減小響應時間,提高用戶體驗。這種方式並不適用於以下場合:
1. 相同參數可能產生不一樣結果的狀況(包含隨機數之類的)
2. 運算結果佔用特別多內存的狀況

不要在循環中建立函數

這個很好理解,每建立一個函數對象是須要大批量空間的。因此在一個循環中建立函數是很不明智的,儘可能將函數移動到循環以前建立,好比以下代碼:

for(var i = 0, m = data.length; i < m; i++) {
    handlerData(data[i], function(data){
        //do something
    });
}

就能夠修改成:

var handler = function(data){
    //do something
};
for(var i = 0, m = data.length; i < m; i++) {
    handlerData(data[i], handler);
}

讓垃圾回收器回收那些再也不須要的對象

以前我曾在 淺談V8引擎中的垃圾回收機制 中講到了V8引擎如何進行垃圾回收。能夠從中看到,若是長時間保存對象,老生代中佔用的空間將增大,每次在老生代中的垃圾回收過程將會至關漫長。而垃圾回收器判斷一個對象爲活對象仍是死對象,是按照是否有活對象或根對象含有對它的引用來斷定的。若是有根對象或者活對象引用了這個對象,它將被斷定爲活對象。因此咱們須要經過手動消除這些引用來讓垃圾回收器對回收這些對象。

delete

一種方式是經過delete方式來消除對象中的鍵值對,從而消除引用。但這種方式並不提倡,它會改變對象的結構,可能致使引擎中對對象的存儲方式變動,降級爲字典方式進行存儲(詳細請見V8 之旅:對象表示),不利於JavaScript引擎的優化,因此儘可能減小使用

null

另外一種方式是經過將值設爲null來消除引用。經過將變量或對象的屬性設爲null,能夠消除引用,使本來引用的對象成爲一個「孤島」,而後在垃圾回收的時候對其進行回收。這種方式不會改變對象的結構,比使用delete要好

全局對象

另外須要注意的是,垃圾回收器認爲根對象永遠是活對象,永遠不會對其進行垃圾回收。而全局對象就是根對象,因此全局做用域中的變量將會一直存在

事件處理器的回收

在日常寫代碼的時候,咱們常常會給一個DOM節點綁定事件處理器,但有時候咱們不須要這些事件處理器後,就無論它們了,它們默默的在內存中保存着。因此在某些DOM節點綁定的事件處理器不須要後,咱們應當銷燬它們。同時綁定的時候也儘可能使用事件代理的方式進行綁定,以避免形成屢次重複的綁定致使內存空間的浪費,事件代理可見前端性能優化(DOM操做篇)

閉包致使的內存泄露

JavaScript的閉包能夠說便是「天使」又是「魔鬼」,它「天使」的一面是咱們能夠經過它突破做用域的限制,而其魔鬼的一面就是和容易致使內存泄露,好比以下狀況:

var result = (function() {
    var small = {};
    var big = new Array(10000000);
    //do something
    return function(){
        if(big.indexOf("someValue") !== -1) {
            return null;
        } else {
            return small;
        }
    }
})();

這裏,建立了一個閉包。使得返回的函數存儲在result中,而result函數可以訪問其做用域內的small對象和big對象。因爲big對象和small對象均可能被訪問,因此垃圾回收器不會去碰這兩個對象,它們不會被回收。咱們將上述代碼改爲以下形式:

var result = (function() {
    var small = {};
    var big = new Array(10000000);
    var hasSomeValue;
    //do something
    hasSomeValue = big.indexOf("someValue") !== -1;
    return function(){
        if(hasSomeValue) {
            return null;
        } else {
            return small;
        }
    }
})();

這樣,函數內部只可以訪問到hasSomeValue變量和small變量了,big沒有辦法經過任何形式被訪問到,垃圾回收器將會對其進行回收,節省了大量的內存。

慎用eval和with

Douglas Crockford將eval比做魔鬼,確實在不少方面咱們能夠找到更好地替代方式。使用它時須要在運行時調用解釋引擎對eval()函數內部的字符串進行解釋運行,這須要消耗大量的時間。像FunctionsetIntervalsetTimeout也是相似的

Douglas Crockford也不建議使用with,with會下降性能,經過with包裹的代碼塊,做用域鏈將會額外增長一層,下降索引效率

對象的優化

緩存須要被使用的對象

JavaScript獲取數據的性能有以下順序(從快到慢):變量獲取 > 數組下標獲取(對象的整數索引獲取) > 對象屬性獲取(對象非整數索引獲取)。咱們能夠經過最快的方式代替最慢的方式:

var body = document.body;
var maxLength = someArray.length;
//...

須要考慮,做用域鏈和原型鏈中的對象索引。若是做用域鏈和原型鏈較長,也須要對所須要的變量繼續緩存,不然沿着做用域鏈和原型鏈向上查找時也會額外消耗時間

緩存正則表達式對象

須要注意,正則表達式對象的建立很是消耗時間,儘可能不要在循環中建立正則表達式,儘量多的對正則表達式對象進行復用

考慮對象和數組

在JavaScript中咱們可使用兩種存放數據:對象和數組。因爲JavaScript數組能夠存聽任意類型數據這樣的靈活性,致使咱們常常須要考慮什麼時候使用數組,什麼時候使用對象。咱們應當在以下狀況下作出考慮:
1. 存儲一串相同類型的對象,應當使用數組
2. 存儲一堆鍵值對,值的類型多樣,應當使用對象
3. 全部值都是經過整數索引,應當使用數組

數組使用時的優化

  1. 往數組中插入混合類型很容易下降數組使用的效率,儘可能保持數組中元素的類型一致
  2. 若是使用稀疏數組,它的元素訪問將遠慢於滿數組的元素訪問。由於V8爲了節省空間,會將稀疏數組經過字典方式保存在內存中,節約了空間,但增長了訪問時間

對象的拷貝

須要注意的是,JavaScript遍歷對象和數組時,使用for...in的效率至關低,因此在拷貝對象時,若是已知須要被拷貝的對象的屬性,經過直接賦值的方式比使用for...in方式要來得快,咱們能夠經過定一個拷貝構造函數來實現,好比以下代碼:

function copy(source){
    var result = {};
    var item;
    for(item in source) {
        result[item] = source[item];
    }
    return result;
}
var backup = copy(source);

可修改成:

function copy(source){
    this.property1 = source.property1;
    this.property2 = source.property2;
    this.property3 = source.property3;
    //...
}
var backup = new copy(source);

字面量代替構造函數

JavaScript能夠經過字面量來構造對象,好比經過[]構造一個數組,{}構造一個對象,/regexp/構造一個正則表達式,咱們應當盡力使用字面量來構造對象,由於字面量是引擎直接解釋執行的,而若是使用構造函數的話,須要調用一個內部構造器,因此字面量略微要快一點點。

緩存AJAX

曾經聽過一個訪問時間比較(固然不精確):
* cpu cache ≈ 100 * 寄存器
* 內存 ≈ 100 * cpu cache
* 外存 ≈ 100 * 內存
* 網絡 ≈ 100 * 外存

可看到訪問網絡資源是至關慢的,而AJAX就是JavaScript訪問網絡資源的方式,因此對一些AJAX結果進行緩存,能夠大大減小響應時間。那麼如何緩存AJAX結果呢

函數緩存

咱們可使用前面緩存複雜計算函數結果的方式進行緩存,經過在函數對象上構造cache對象,原理同樣,這裏略過。這種方式是精確到函數,而不精確到請求

本地緩存

HTML5提供了本地緩存sessionStorage和localStorage,區別就是前者在瀏覽器關閉後會自動釋放,然後者則是永久的,不會被釋放。它提供的緩存大小以MB爲單位,比cookie(4KB)要大得多,因此咱們能夠根據AJAX數據的存活時間來判斷是存放在sessionStorage仍是localStorage當中,在這裏以存儲到sessionStorage中爲例(localStorage只需把第一行的window.sessionStorage修改成window.localStorage):

function(data, url, type, callback){
    var storage = window.sessionStorage;
    var key = JSON.stringify({
        url : url,
        type : type,
        data : data
    });
    var result = storage.getItem(key);
    var xhr;
    if (result) {
        callback.call(null, result);
    } else {
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status === 200){
                    storage.setItem(key, xhr.responseText);
                    callback.call(null, xhr.responseText);
                } else {
                }
            }
        };
        xhr.open(type, url, async);
        xhr.send(data);
    }
};

使用布爾表達式的短路

在不少語言中,若是bool表達式的值已經能經過前面的條件肯定,那麼後面的判斷條件將再也不會執行,好比以下代碼

function calCondition(params) {
    var result;
    //do lots of work
    return !!result;
}

if(otherCondition && calCondition(someParams)) {
    console.log(true);
} else {
    console.log(false);
}

這裏首先會計算otherCondition的值,若是它爲false,那麼整個正則表達式就爲false了,後續的須要消耗大量時間的calCondition()函數就不會被調用和計算了,節省了時間

使用原生方法

在JavaScript中,大多數原生方法是使用C++編寫的,比js寫的方法要快得多,因此儘可能使用諸如Math之類的原生對象和方法

字符串拼接

在IE和FF下,使用直接+=的方式或是+的方式進行字符串拼接,將會很慢。咱們能夠經過Array的join()方法進行字符串拼接。不過並非全部瀏覽器都是這樣,如今不少瀏覽器使用+=比join()方法還要快

使用web worker

web worker是HTML5提出的一項新技術,經過多線程的方式爲JavaScript提供並行計算的能力,經過message的方式進行相互之間的信息傳遞,我尚未仔細研究過

JavaScript文件的優化

使用CDN

在編寫JavaScript代碼中,咱們常常會使用庫(jQuery等等),這些JS庫一般不會對其進行更改,咱們能夠將這些庫文件放在CDN(內容分發網絡上),這樣能大大減小響應時間

壓縮與合併JavaScript文件

在網絡中傳輸JS文件,文件越長,須要的時間越多。因此在上線前,一般都會對JS文件進行壓縮,去掉其中的註釋、回車、沒必要要的空格等多餘內容,若是經過uglify的算法,還能夠縮減變量名和函數名,從而將JS代碼壓縮,節約傳輸時的帶寬。另外常常也會將JavaScript代碼合併,使全部代碼在一個文件之中,這樣就可以減小HTTP的請求次數。合併的原理和sprite技術相同

使用Application Cache緩存

這個在以前的文章前端性能優化(Application Cache篇)中已有描述,就不贅述了

相關文章
相關標籤/搜索