javascript效能優化(8)

Presbyterian發表於2018-03-14

Programming Practices 程式設計實踐

避免二次評估

JavaScript 與許多指令碼語言一樣,允許你在程式中獲取一個包含程式碼的字串然後執行它。有四種標準 方法可以實現:eval(),Function()構造器,setTimeout()和 setInterval()。每個函式允許你傳入一串 JavaScript 程式碼,然後執行它。例如

var num1 = 5, num2 = 6, //eval_r() evaluating a string of code
        result = eval("num1 + num2"), //Function() evaluating strings of code
        sum = new Function("arg1", "arg2", "return arg1 + arg2"); //setTimeout()evaluating a string of code
    setTimeout("sum = num1 + num2", 100); //setInterval() evaluating a string of code
    setInterval("sum = num1 + num2", 100);

結果都是11

避免二次評估是實現優化的 JavaScript 執行時效能的關鍵。
作為一個比較點,不同瀏覽器上訪問一個陣列項所佔用的時間各有不同,但如果使用 eval()訪問其結 果將大相徑庭。例如:

 //faster 
 var item = array[0]; 
 //slower 
 var item = eval_r("array[0]"); 

若使用 eval()代替直接程式碼訪問 10’000 個陣列項,在不同瀏覽器上的差異非常巨大。是因為每次呼叫 eval()時要建立一個新的解釋/編譯例項。同樣的過程 也發生在 Function(),setTimeout()和 setInterval()上,自動使程式碼執行速度變慢。
大多數情況下,沒必要使用 eval()或 Function(),如果可能的話,儘量避免使用它們。至於另外兩個函 數,setTimeout()和 setInterval(),建議第一個引數傳入一個函式而不是一個字串。例如:

    setTimeout(function () {
        sum = num1 + num2;
    }, 100);
    setInterval(function () {
        sum = num1 + num2;
    }, 100);

使用物件/陣列直接量

典型的物件建立和賦值是這樣的:

     //create an object 
    var myObject = new Object();
    myObject.name = "Nicholas";
    myObject.count = 50;
    myObject.flag = true;
    myObject.pointer = null;
    //create an array 
    var myArray = new Array();
    myArray[0] = "Nicholas";
    myArray[1] = 50;
    myArray[2] = true;
    myArray[3] = null;

但是,直接量賦值很快。作為一個額外的好處,直接量在你的程式碼中佔 用較少空間,所以整個檔案尺寸可以更小。

    //create an object 
    var myObject = {name: "Nicholas", count: 50, flag: true, pointer: null};
    //create an array 
    var myArray = ["Nicholas", 50, true, null];

不要重複工作

也許常見的重複工作型別是瀏覽器檢測。大量程式碼依賴於瀏覽器的功能。以事件控制程式碼的新增和刪除為 例,典型的跨瀏覽器程式碼如下:

function addHandler(target, eventType, handler) {
        if (target.addEventListener) { //DOM2 Events    
            target.addEventListener(eventType, handler, false);
        } else { //IE     
            target.attachEvent("on" + eventType, handler);
        }
    }
    function removeHandler(target, eventType, handler) {
        if (target.removeEventListener) { //DOM2 Events    
            target.removeEventListener(eventType, handler, false);
        } else { //IE     
            target.detachEvent("on" + eventType, handler);
        }
    }

乍一看,這些函式為實現它們的目的已經足夠優化。隱藏的效能問題在於每次函式呼叫時都執行重複工 作。每一次,都進行同樣的檢查,看看某種方法是否存在。如果你假設 target 唯一的值就是 DOM 物件, 而且使用者不可能在頁面載入時魔術般地改變瀏覽器,那麼這種判斷就是重複的。如果 addHandler()一上來 就呼叫addEventListener()那麼每個後續呼叫都要出現這句程式碼。在每次呼叫中重複同樣的工作是一種浪費, 有多種辦法避免這一點。

延遲載入

第一種消除函式中重複工作的方法稱作延遲載入。延遲載入意味著在資訊被使用之前不做任何工作。在
前面的例子中,不需要判斷使用哪種方法附加或分離事件控制程式碼,直到有人呼叫此函式。使用延遲載入的函
數如下:

 function addHandler(target, eventType, handler) {   
        //overwrite the existing function   
        if (target.addEventListener) { //DOM2 Events    
            addHandler = function (target, eventType, handler) {
                target.addEventListener(eventType, handler, false);
            };
        } else { //IE     
            addHandler = function (target, eventType, handler) {
                target.attachEvent("on" + eventType, handler);
            };
        }   
        //call the new function 
        addHandler(target, eventType, handler);
    }
    function removeHandler(target, eventType, handler) {   
        //overwrite the existing function   
        if (target.removeEventListener) { //DOM2 Events     
            removeHandler = function (target, eventType, handler) {
                target.addEventListener(eventType, handler, false);
            };
        } else { //IE     
            removeHandler = function (target, eventType, handler) {
                target.detachEvent("on" + eventType, handler);
            };
        }   
        //call the new function   
        removeHandler(target, eventType, handler);
    }

這兩個函式依照延遲載入模式實現。這兩個方法第一次被呼叫時,檢查一次並決定使用哪種方法附加或分離事件控制程式碼。然後,原始函式就被包含適當操作的新函式覆蓋了。後呼叫新函式並將原始引數傳給它。以後再呼叫 addHandler()或者 removeHandler()時不會再次檢測,因為檢測程式碼已經被新函式覆蓋了。
呼叫一個延遲載入函式總是在第一次使用較長時間,因為它必須執行檢測然後呼叫另一個函式以完成任務。但是,後續呼叫同一函式將快很多,因為不再執行檢測邏輯了。延遲載入適用於函式不會在頁面上立即被用到的場合。

條件預載入

它在指令碼載入之前提前進行檢查,而不等待函式呼叫。 這樣做檢測仍只是一次,但在此過程中來的更早。例如:

 var addHandler = document.body.addEventListener ? function (target, eventType, handler) {
            target.addEventListener(eventType, handler, false);
        } : function (target, eventType, handler) {
            target.attachEvent("on" + eventType, handler);
        };
    var removeHandler = document.body.removeEventListener ? function (target, eventType, handler) {
            target.removeEventListener(eventType, handler, false);
        } : function (target, eventType, handler) {
            target.detachEvent("on" + eventType, handler);
        };

這個例子檢查 addEventListener()和 removeEventListener()是否存在,然後根據此資訊指定合適的函式。 三元操作符返回 DOM 級別 2 的函式,如果它們存在的話,否則返回 IE 特有的函式。然後,呼叫 addHandler() 和 removeHandler()同樣很快,雖然檢測功能提前了。
條件預載入確保所有函式呼叫時間相同。其代價是在指令碼載入時進行檢測。預載入適用於一個函式馬上 就會被用到,而且在整個頁面生命週期中經常使用的場合。

位操作運算子

計算對 2 取模,需要用這個數除以 2 然後檢視餘數。如果你看到 32 位數字的底層(二進位制)表示法, 你會發現偶數的低位是 0,奇數的低位是 1。如果此數為偶數,那麼它和 1 進行位與操作的結果就是 0; 如果此數為奇數,那麼它和 1 進行位與操作的結果就是 1。也就是說上面的程式碼可以重寫如下:

     for (var i = 0, len = rows.length; i < len; i++) {
        if (i & 1) {
            className = "odd";  //奇數
        } else {
            className = "even";//偶數
        }
        //apply class
    }

比取餘運算要快;i%2;


第二種使用位操作的技術稱作位掩碼。位掩碼在電腦科學中是一種常用的技術,可同時判斷多個布林 選項,快速地將數字轉換為布林標誌陣列。掩碼中每個選項的值都等於 2 的冪。例如:

    var OPTION_A = 1;
    var OPTION_B = 2;
    var OPTION_C = 4;
    var OPTION_D = 8;
    var OPTION_E = 16;
    var options = OPTION_A | OPTION_C | OPTION_D;
    //is option A in the list?
    if (options & OPTION_A) {
        //do something
        console.log(options);
    }
    //is option B in the list?
    if (options & OPTION_D) {
        //do something
        console.log(options);
    }

結果:13 13
像這樣的位掩碼操作非常快,正因為前面提到的原因,操作發生在系統底層。如果許多選項儲存在一起 並經常檢查,位掩碼有助於加快整體效能。


原生方法

  • 儘量使用Math物件,避免大量的數學邏輯;
  • 使用原生的選擇器 API;如 querySelector()和 querySelectorAll();

Summary 總結

  • 通過避免使用 eval_r()和 Function()構造器避免二次評估。此外,給 setTimeout()和 setInterval()傳遞函式參 數而不是字串引數
  • 建立新物件和陣列時使用物件直接量和陣列直接量。它們比非直接量形式建立和初始化更快。
  • 避免重複進行相同工作。當需要檢測瀏覽器時,使用延遲載入或條件預載入。
  • 當執行數學運算時,考慮使用位操作,它直接在數字底層進行操作。
  • 原生方法總是比 JavaScript 寫的東西要快。儘量使用原生方法。

相關文章