天天看點

high performence javascript(高性能JavaScript)第一章 加載和執行資料存取DOM 程式設計建立快速相應的使用者界面程式設計實戰

本書推薦指數:4星

第一章 加載和執行

  1. 浏覽器使用單一線程來處理使用者界面(UI)重新整理和JavaScript腳本,同一時刻隻能做一件事。當浏覽器在執行JavaScript代碼時不能做其他的事情,比如相應使用者點選按鈕、重新整理UI等
  2. 無論JavaScript代碼是内嵌還是包含在外部檔案中,頁面的下載下傳和渲染都必須要停下來等待腳本執行完成
  3. 浏覽器都允許并行下載下傳js檔案,但是js下載下傳過程中仍然會阻塞其他資源的下載下傳,比如圖檔。盡管腳本的下載下傳過程不會互相影響,但頁面仍然要等到所有的js代碼下載下傳并執行完畢才能繼續。
  4. 無阻塞的腳本:
    • defer和async。相同點是采用并行下載下傳,在下載下傳過程中不會阻塞。差別在于執行時機:async是在加載完成後立馬自動執行,而defer是并行下載下傳之後再onload事件之前執行。(注意标準規定defer隻能在外部腳本中起作用)
    • 動态腳本元素:
      var script = document.createElement("script");
      script.type = "type/javascript";
      script.src = "xxx.js";
      document.getElementsByTagName("head")[].appendChild("script");
                 
      文本在被添加到頁面時開始下載下傳并立即執行,但不會阻塞頁面其他程序。
    • XMLHttpRequest 腳本注入

      使用XMLHttpRequest對象擷取腳本并注入到頁面中

      var xhr = new XMLHttpRequest();
      xhr.open("get", "file1.js", true);
      xhr.onreadystatechange() {
          if(xhr.readyState == ) {
              if(xhr.staus >=  && xhr.status <  || xhr.status == ) {
                  var script = document.createElement("script");
                  script.type = "type/javascript";
                  script.text = xhr.responseText;
                  document.body.appendChild(script);
              }
          }
      }
                 

資料存取

js中有四種基本的資料存取位置:

  1. 字面量:字元串、數字、布爾值、對象、數組、函數、正規表達式、null、undefined
  2. 本地變量:用var定義的資料存儲單元
  3. 數組元素:存儲在js數組對象内部,以數字為索引
  4. 對象成員:存儲在js對象内部,以字元串為索引

    字面量和本地變量的通路速度快于數組項和對象成員的通路速度

辨別符解析的性能:

  1. 一個辨別符所在的位置越深,讀取的速度也就越慢。是以函數中讀取局部變量總是最快的,而讀取全局變量總是最慢的。
  2. 如果某個跨作用域的值在函數中被引用一次以上,那麼就把它存儲到局部變量裡。

改變作用域鍊:

  • with
  • try-catch語句中catch從句

    他們的本質都是把一個新的變量對象插入到作用域鍊的首部,然後函數的局部變量就處于第二個作用域鍊對象中,是以通路的代價更高了。

    使用with時,一個新的變量對象被建立,它包含了參數指定的對象的所有屬性,此對象被推入作用域鍊的首部。catch從句是把一個錯誤對象插入到作用域鍊的首部

DOM 程式設計

文檔對象模型(DOM)是一個獨立于語言的,用于操作XML和HTML文檔的程式接口,使用DOM API來通路文檔中的資料

操作DOM很慢:把DOM和ECMAScript比作是一個島嶼,它們之間用收費橋梁連接配接,ECMAScript每次通路DOM都需要成本。

最佳實踐:減少通路DOM的次數,把運算盡量留在ECMAScript這一端處理

隻傳回元素節點的API

忽略注釋和文本節點(通常隻是兩個節點間的空白)

children                childNodes
childElementCount       childNodes.length
firstElementChild       firstChild
lastElementChild        lastChild
nextElementSibling      nextSibling
previousElementSibling  previousSibling
           

使用高效的選擇器API

querySelector();querySelectorAll();
           

重繪與重排

浏覽器将頁面中所有元件下載下傳完成之後會生成兩個内部資料結構:

DOM樹:表示頁面結構

渲染樹:表示DOM節點如何顯示

DOM樹中的一個節點至少對應渲染樹中的一個節點(隐藏的DOM元素在渲染樹中沒有對應的節點),DOM樹和渲染樹建構完成,浏覽器就開始繪制頁面元素

并不是所有的DOM變化都會影響幾何屬性,比如改變一個元素的背景色并不會影響其位置和大小,這是隻會觸發重繪而不需要重排

重排何時發生:

  1. 添加或删除可見的DOM元素
  2. 元素位置改變
  3. 元素大小改變(如margin、border、padding、width、height改變)
  4. 内容改變:文本改變或圖檔被另一個不同尺寸的圖檔替代
  5. 頁面渲染器初始化
  6. 浏覽器視窗尺寸變化

最小化重排與重繪

改變樣式:使用cssText或css類

var el = document.getElementById("mydiv");
        el.style.borderLeft = "1px";
        el.style.borderRight = "2px";
        el.style.padding = "5px";

        //改進:使用cssText
        var el = document.getElementById("mydiv");
        el.style.cssText = "border-left: 1px; border-right: 2px; padding: 5px;";

        //另一種改進方式:使用js操作css類,做到js和css分離
        var el = document.getElementById("mydiv");
        el.className = "xxx";
        //el.classList.toggle("xxx");
           

批量修改DOM元素,減少重排和重繪

步驟:

  1. 使元素脫離文檔流
  2. 對元素應用多重改變
  3. 把元素帶回文檔中

有三種方法使元素脫離文檔流

1. 隐藏元素,應用修改,重新顯示

2. 使用文檔片斷(documentFragment),在DOM之外構造一個子樹,然後把它拷貝回文檔

3. 将原始元素拷貝到一個脫離文檔的副本中,修改副本,完成後再替換原始元素

建立快速相應的使用者界面

js的單個運作任務不要超過100ms,否則會讓使用者感覺到明顯的緩慢

解決辦法:

1. 使用定時器分割任務

2. 使用worker在UI線程之外運作代碼(建立多線程)

程式設計實戰

避免雙重求值

js中運作包含代碼的字元串有四種方式:

1. eval()

2. function()構造函數

3. setTiemeout()

4. setInterval()

他們允許傳入一個js代碼字元串并執行它

var num1 = ,
    num2 = ,
resEval = eval("num1 + num2");
resFunction = new Function("arg1", "arg2", "return arg1 + arg2");
function sum() {
    console.log(num1 + num2);
};
console.log(resEval);
console.log(resFunction(num1, num2));

setTimeut(sum, );       //使用此種方式而非下一行的方式
setTimeout("sum()", );
//setInterval()函數同理
           

以上四種方法都是在JavaScript代碼中執行另一段JavaScript代碼,這回導緻雙重求值的性能消耗,此代碼先會正常求值,然後在執行過程中對包含在字元串中的代碼發起另一次求值運算。

使用Object/Array直接量而不是對象

對象字面量和數組字面量運作更快,有助于節省代碼量

避免重複工作

還記得時間處理時候的eventUtil對象嗎?

addHandler: function(element, type, handler) {
        if(element.addEventListener) {
            element.addEventListener(type, handler, false);
        } else if(element.attachEvent) {
            element.attachEvent('on'+type, handler);
        } else {
            element['on'+type] = handler;
        }
    },
    /*
    上一種方式的問題在于每一次運作addHandler函數的時候都要進行能力檢測,然而第二次
    檢測是完全沒有必要的,故采用延遲加載模式,方法在第一次被調用時檢查使用哪種事件處理程式。然後原始函數被包含正确操作的新函數覆寫。

    addHandler: function(element, type, handler) {
        //重寫函數
        if(element.addEventListener) {
            addHandler = function(element, type, handler) {
                element.addEventListener(type, handler, false);
            }
        } else if(element.attachEvent) {
            addHandler = function(element, type, handler) {
                element.attachEvent('on'+type, handler);
            }
        } else {
            addHandler = function(element, type, handler) {
                element['on'+type] = handler;
            }
        }
        //調用函數
        addHandler(element, type, handler);
    },*/
           

使用位運算加快速度

按位與

&

按位或

|

按位異或

^

按位取反

~

比如判斷奇偶

var i = ;
if(i % ) {             //odd
    console.log("odd");
} else {                //even
    console.log("even");
}

//使用位運算判斷更快!
if(i & ) {             //odd
    console.log("odd");
} else {                //even
    console.log("even");
}
/**
 如果函數是奇數,那麼其最低位是1,與1&的結果是1(true)
 如果函數是偶數,那麼其最低位是0,與1&的結果是0(false)
 */
           

繼續閱讀