天天看點

JavaScript 性能優化——惰性載入函數

原文首發于 Guanngxu 的個人部落格:JavaScript 性能優化——惰性載入函數

參考資料:

《JavaScript 進階程式設計(第三版)》

JavaScript專題之惰性函數

深入了解javascript函數進階之惰性函數

因為不同廠商的浏覽器互相之間存在一些行為上的差異,很多 js 代碼包含了大量的

if

語句,将執行引導到正确的分支代碼中去,比如下面的例子。

function createXHR() {
    if (typeof XMLHttpRequest != 'undefined') {
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != 'undefined') {
        if (typeof arguments.callee.activeXString != 'string') {
            var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
            var i, len;
            for (i = 0, len = versions.length; i < len; i++) {
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                } catch (e) {
                    // skip
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error('No XHR object available.');
    }
}
           

我們可以發現,在浏覽器每次調用

createXHR()

的時候,它都要對浏覽器所支援的能力仔細檢查,但是很明顯當第一次檢查之後,我們就應該知道浏覽器是否支援我們所需要的能力,是以除第一次之外的檢查都是多餘的。即使隻有一個

if

語句也肯定要比沒有

if

語句慢,是以

if

語句不必每次都執行,那麼代碼可以運作的更快一些,惰性載入就是用來解決這種問題的技巧。

函數重寫

要了解惰性載入函數的原理,我們有必要先了解一下函數重寫技術,由于一個函數可以傳回另一個函數,是以可以在函數内部用新的函數來覆寫舊的函數。

function sayHi() {
    console.info('Hi');
    sayHi = function() {
        console.info('Hello');
    }
}
           

我們第一次調用

sayHi()

函數時,控制台會列印出

Hi

,全局變量

sayHi

被重新定義,被賦予了新的函數,從第二次開始之後的調用都會列印出

Hello

。惰性載入函數的本質就是函數重寫,惰性載入的意思就是函數執行的分支隻會發生一次。

惰性載入

我們來看一個例子(例子來源于冴羽所寫的JavaScript專題之惰性函數)。現在需要寫一個

foo

函數,這個函數傳回首次調用時的

Date

對象,注意是首次。

方案一
var t;
function foo() {
    if (t) return t;
    t = new Date()
    return t;
}
// 此方案存在兩個問題,一是污染了全局變量
// 二是每次調用都需要進行一次判斷
           
方案二
var foo = (function() {
    var t;
    return function() {
        if (t) return t;
        t = new Date();
        return t;
    }
})();
// 使用閉包來避免污染全局變量,
// 但是還是沒有解決每次調用都需要進行一次判斷的問題
           
方案三
function foo() {
    if (foo.t) return foo.t;
    foo.t = new Date();
    return foo.t;
}
// 函數也是一種對象,利用這個特性也可以解決
// 和方案二一樣,還差一個問題沒有解決
           
方案四
var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};
// 利用惰性載入技巧,即重寫函數
           

惰性載入函數有兩種實作方式,第一種是在函數被調用時再處理函數。在第一次調用的過程中,該函數會被覆寫為另外一種按合适方式執行的函數,這樣任何對原函數的調用都不用再經過執行分支了。

第二種實作方式是在聲明函數時就指定适當的函數。這樣第一次調用時就不會損失性能了,而是在代碼首次加載時會損失一點性能,即是利用閉包寫一個自執行的函數。

改進 createXHR

有了上面的基礎,我們就可以将

createXHR()

改進為下列形式,這樣就不用每次調用都進行判斷了。

// 第一種實作方式
function createXHR() {
    if (typeof XMLHttpRequest != 'undefined') {
        createXHR = function() {
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject != 'undefined') {
        createXHR = function() {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
                var i, len;
                for (i = 0, len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        createXHR = function() {
            throw new Error('No XHR object available.');
        }
    }
}

// 第二種實作方式
function createXHR() {
    if (typeof XMLHttpRequest != 'undefined') {
        return function() {
            return new XMLHttpRequest();
        }
    } else if (typeof ActiveXObject != 'undefined') {
        return function() {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'];
                var i, len;
                for (i = 0, len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function() {
            throw new Error('No XHR object available.');
        }
    }
}
           

個人微信:Guanngxu

JavaScript 性能優化——惰性載入函數