1. 程式人生 > >JavaScript性能優化 DOM編程

JavaScript性能優化 DOM編程

http響應 對象 eight war src 查看 javascrip 事件 時間

最近在研讀《高性能JavaScript》,在此做些簡單記錄。示例代碼可在此處查看到。

一、DOM

1)DOM和JavaScript

文檔對象模型(DOM)是一個獨立於語言的,用於操作XML和HTML文檔的程序接口(API)。

瀏覽器通常會把DOM和JavaScript獨立實現。例如Chrome中使用Webkit的WebCore庫渲染頁面,用V8作為JavaScript引擎。

訪問DOM天生就慢,將DOM和JavaScript比喻為兩個島嶼,兩處同行要收過橋費,ECMAScript訪問DOM的次數越多,過橋費越貴,因此推薦的做法是盡可能減少過橋的次數。

技術分享

2)性能測試

下圖是對兩段代碼的性能測試,可在此處查看在線性能測試

1. 第一段每一次循環都直接用DOM賦值

2. 第二段是先將內容緩存到局部變量中,最後使用一次DOM賦值。

技術分享

測試結果以每秒鐘執行測試代碼的次數(Ops/sec)顯示,這個數值越大越好。

除了這個結果外,同時會顯示測試過程中的統計誤差,以及相對最好的慢了多少(%),統計誤差也是非常重要的指標。

二、選擇器API

有時候為了得到需要的元素列表,需要組合調用getElementById等並遍歷返回的節點,但這種繁密的過程效率低下。

使用CSS選擇器是一種定位節點的便利途徑,querySelectorAll就是DOM的原生方法。

//DOM組合API
var elements = document.getElementById(‘menu‘).getElementsByTagName(‘a‘);
//替換為簡便的CSS選擇器 var elements = document.querySelectorAll(‘#menu a‘);

三、重繪與重排

在《CSS動畫與JavaScript動畫》中層提到過頁面渲染的一般過程為JavaScript > 計算樣式 > 布局 > 繪制 > 渲染層合並。

技術分享

Layout(重排)和Paint(重繪)是整個環節中最為耗時的兩環,所以我們盡量避免著這兩個環節。

當DOM的變化影響了元素的幾何屬性(寬和高)將會發生重排(reflow);

完成重排後,瀏覽器會重新繪制受影響的部分到屏幕中,此過程為重繪(repaint)。

1)重排何時發生

1. 添加或刪除可見的DOM元素

2. 元素位置改變

3. 元素尺寸改變(包括外邊距、內邊距、邊框寬度、寬、高等屬性)

4. 內容改變,例如文本改變或圖片被不同尺寸的替換掉。

5. 頁面渲染器初始化。

6. 瀏覽器窗口尺寸改變。

2)批量執行重排

下面代碼看上去會重排3次,但其實只會重排1次,大多數瀏覽器通過隊列化修改和批量顯示優化重排版過程。

技術分享

//渲染樹變化的排隊和刷新
var ele = document.getElementById(‘myDiv‘);
ele.style.borderLeft = ‘1px‘;
ele.style.borderRight = ‘2px‘;
ele.style.padding = ‘5px‘;

但下列操作將會強迫隊列刷新並要求所有計劃改變的部分立刻應用:

offsetTop, offsetLeft, offsetWidth, offsetHeight 
scrollTop, scrollLeft, scrollWidth, scrollHeight 
clientTop, clientLeft, clientWidth, clientHeight 
getComputedStyle() (currentStyle in IE)(在 IE 中此函數稱為 currentStyle) 

像offsetHeight屬性需要返回最新的布局信息,因此瀏覽器不得不執行渲染隊列中的“待處理變化”並觸發重排以返回正確的值。

對於尺寸坐標相關的信息可以參考《JavaScript中尺寸、坐標》。

3)最小化重繪和重排

1. cssText和class

cssText可以一次設置多個CSS屬性。class也可以一次性設置,並且更清晰,更易於維護,但有前提條件,就是不依賴於運行邏輯和計算的情況。

// cssText
ele.style.cssText = ‘border-left: 1px; border-right: 2px; padding: 5px;‘;
// class
ele.className = ‘active‘;

在《JavaScript特性(attribute)、屬性(property)和樣式(style)》詳細介紹了CSS相關的JS操作。

2. 批量修改DOM

2.1 隱藏元素display:none,應用修改,重新顯示display:block

2.2 使用文檔片段fragment,在片段上操作節點,再拷貝回文檔。

//文檔片段(fragment)
var fragment = document.createDocumentFragment();
var li = document.createElement(‘li‘);
li.innerHTML = ‘banana‘;
fragment.appendChild(li);
document.getElementById(‘fruit‘).appendChild(fragment);

2.3 將原始元素拷貝到一個脫離文檔的節點中(例如position:absolute),修改副本,完成後再替換原始元素。

四、其它章節性能介紹

1)第一章 加載和執行

將<script>標簽放到頁面底部,也就是</body>閉合標簽之前。

多種無阻塞下載JavaScript的方法:

1. 使用<script>標簽的defer屬性。頁面解析到<script>標簽時開始下載,但並不會執行,直到DOM加載完(onload事件觸發前)。

2. 動態創建<script>元素來下載並執行代碼。無論在何時啟動下載,文件的下載和執行過程不會阻塞頁面其它進程,但返回的代碼通常會立刻執行。

3. 使用XHR對象下載JavaScript代碼並註入頁面中。優點是下載後不會自動執行,所有主流瀏覽器都支持,但不能跨域下載。

2)第二章 數據存取

1. 每遇到一個變量,就會經歷一次標識符解析的過程,以決定在哪裏獲取和存儲數據。在執行環境的作用域中,標識符所在的位置越深,讀寫速度也就越慢。因此函數中讀寫局部變量是最快的,讀寫全局變量是最慢的。

2. 由於對象成員可能包含其它成員,例如window.location.href。每次遇到點操作符,嵌套成員會導致JavaScript引擎搜索全部對象成員。對象成員嵌套越深,讀取速度越慢location.href就比window.location.href快。

3)第五章 字符串與正則表達式

str = str + "one";//性能高
str = "one" + str;//性能差

1. 除IE外,瀏覽器會簡單的將第二個字符串拷貝到第一個的後面,如果變量str很大的話,就會出現性能損耗(內存占用)就會很高。

2. 正則優化包括:減少分支數量,縮小分支範圍;使用非捕獲數組;只捕獲感興趣的文本以減少後期處理;使用合適的量詞;化繁為簡,分解復雜的正則;

4)第六章 快速響應的用戶界面

使用定時器讓出時間片段,分割大型任務,在文章《JavaScript定時器分析》中有具體分析。

5)第七章 Ajax

Mutipart XHR允許客戶端只用一個HTTP請求就可以從服務器向客戶端傳送多個資源。

將資源文件(CSS、HTML、JavaScript、Base64編碼圖片)打包成一個由雙方約定的字符串分隔符,發送到客戶端。

然後用JavaScript代碼處理這個字符串,根據“mime-type”類型和傳入的頭信息解析每個資源。

6)第九章 構建並部署高性能JavaScript應用

1. 合並JavaScript文件以減少HTTP請求數。

2. 壓縮JavaScript文件。

3. 在服務器端壓縮JavaScript文件(Gzip編碼)。

4. 正確設置HTTP響應頭來緩存JavaScript文件,通過向文件名增加時間戳避免緩存問題。

5. 使用CDN(Content Delivery Network)提供JavaScript文件。

第八章 編程實踐內容比較多,單獨令出來作為一節。

五、第八章 編程實踐

1)使用Object/Array直接量

代碼例下:

var myObject = { 
  name: "pwstrick", 
  age: 29 
};
var myArr = ["pwstrick", 29];

2)避免重復工作

也就是惰性模式。減少每次代碼執行時的重復性分支判斷,通過對對象重定義來屏蔽原對象中的分支判斷。

惰性模式分為兩種:第一種文件加載後立即執行對象方法來重定義,第二種是當第一次使用方法對象時來重定義。可參考在線demo代碼

在文章《JavaScript設計模式》中有更多的設計模式介紹。

3)位運算

1. 用位運算取代純數學操作,比如對2取模digit%2可以判斷偶數與奇數。

2. 位掩碼技術,使用單個數字的每一位來判斷選項是否成立。掩碼中每個選項的值都是2的冪。例如:

var OPTION_A = 1, OPTION_B = 2, OPTION_C = 4, OPTION_D = 8, OPTION_E = 16;
//用按位或運算創建一個數字來包含多個設置選項
var options = OPTION_A | OPTION_C | OPTION_D;
//接下來可以用按位與操作來判斷給定的選項是否可用
//選項A是否在列表中
if(options & OPTION_A) {
  //...
}

3. 用按位左移(<<)做乘法,用按位右移做除法(>>),例如digit*2可以替換成digit<<2

4)原生方法

無論你的代碼如何優化,都比不上JavaScript引擎提供的原生方法快。

1. 數學運算用內置的Math對象中提供的方法。

2. 用原生的CSS選擇器查找DOM節點,querySelectorquerySelectorAll

參考資料:

高性能JavaScript

利用jsPerf優化Web應用的性能

高性能JavaScript DOM編程

讀《高性能javascript》筆記(一)

高性能JavaScript 重排與重繪

JavaScript性能優化 DOM編程