JavaScript性能優化 DOM編程
最近在研讀《高性能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節點,querySelector或querySelectorAll。
參考資料:
高性能JavaScript
利用jsPerf優化Web應用的性能
高性能JavaScript DOM編程
讀《高性能javascript》筆記(一)
高性能JavaScript 重排與重繪
JavaScript性能優化 DOM編程