高性能JavaScript
author: @TiffanysBearjavascript
從《高性能JavaScript》一書中的整理筆記:css
一、將常用的對象成員、數組項、和域外變量存入局部變量html
緣由:數據存儲位置對大地代碼總體性能會產生重要的影響,直接變量和局部變量的訪問速度快於數組和對象成員。由於局部變量位於做用域鏈的第一個對象中,全局變量位於做用域鏈的最後一環。變量在做用域鏈的位置越深,訪問的時間就越長。java
var doc = document;
var db = doc.body;
var odiv = doc.getElementById('div1');
複製代碼
二、避免使用with表達式,由於他改變了運行期上下文的做用域鏈。node
三、同理with,也要注意使用try-catch,由於catch也會改變運行期上下文的做用域鏈。git
四、嵌套成員變量會形成重大的性能影響,儘可能少用。github
五、DOM操做量化問題:數組
// 在循壞中更新頁面,問題所在:每次循環都對DOM元素訪問了兩次
// 一次是讀取document.getElementById('here').innerHTML的內容
// 一次是修改它。
function changeDOM() {
for (var i=0; i < 15000; i++) {
document.getElementById('here').innerHTML += 'a';
}
}
// 改變方法,使用局部變量存好改變量,在循環結束時一併修改
function changeDOM() {
var content ='';
for (var i=0; i < 15000; i++) {
content += 'a';
}
document.getElementById('here').innerHTML += content;
}
// 關於js字符串拼接的性能優化問題
// js的處理機制是:新建一個臨時字符串,將新字符串賦值爲 content + 'a'
// 而後返回這個新字符串並同時銷燬原始字符串
// 致使字符串的鏈接效率較低的重要緣由不只在於對於新的臨時變量的不斷建立
// 還有js的垃圾回收機制下不斷在對象建立期間回收,致使的效率低下
// 提升效率的辦法是用數組的join函數:
function changeDOM() {
var content =[];
for (var i=0; i < 15000; i++) {
content.push('a');
}
document.getElementById('here').innerHTML += content.join('');
}
// 可是同時也要注意,後來的大部分瀏覽器都對「+」的鏈接字符串作了優化
// 因爲SpiderMonkey等引擎對字符串的「+」運算作了優化,結果使用Array.join的效率反而不如直接用"+"!
// 所以建議是:在IE7如下,使用join,在新瀏覽器下,除了變量緩存外,不須要作別的優化
      
複製代碼
六、克隆已有的DOM元素,即element.cloneNode(),比起新建節點來講,即element.createElement(),會快一點,可是性能提升不是很大。瀏覽器
七、遍歷數組明顯快於一樣大小和內容的HTML集合緩存
八、 for循環時,HTML某元素集合的長度不建議直接做爲循環終止條件,最好將集合的長度賦給一個變量,而後使用變量做爲循環終止條件;
緣由:當每次迭代過程訪問集合的length時,它致使集合器更新,在全部的瀏覽器上都會產生明顯的性能損失。
九、須要考慮實際狀況的優化,根據7,能夠將集合中的元素經過for循壞賦值到數組中,訪問數組的數組快於集合。可是要注意對於複製的開銷是否值得。
function toArray(collection) {
var arr = [];
var clen = collection.length;
for (var i= 0; i < clen; i++) {
arr[i] = collection[i];
}
}
複製代碼
十、獲取DOM節點,使用nextSibling方式與childNodes方式,在不一樣的瀏覽器中,這兩種方法的時間基本相等。可是在IE中,nextSibling比childNodes好,IE6下,nextSibling比對手快16倍,在IE7下,快105倍。所以,在老的IE中性能嚴苛的使用條件下,用nextSibling較好。
十一、querySelectorAll()能夠聯合查詢,即querySelectorAll(‘div .warning,div .notice’),在各大瀏覽器中支持也挺好的,還能夠過濾不少非元素節點;
這個網站是:canIuse,能夠檢查HTML、CSS元素在各大瀏覽器的兼容狀況,一個頗有用的網站!
十二、重繪和重排版;
重繪:不須要改變元素的長度和寬度,不影響DOM的幾何屬性;
重排版:影響了幾何屬性,須要從新計算元素的幾何屬性,並且其餘元素的幾何屬性有可能也會受影響。瀏覽器會在重排版過程當中,從新繪製屏幕上受影響的部分。
獲取佈局信息的操做將致使刷新隊列的動做,如使用:offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
、scrollTop
、scrollLeft
、scrollWidth
、clientTop
、clientLeft
、clientHeight
、geteComputedStyle()
(在IE中此函數成爲currentStyle);瀏覽器此時不得不進行渲染隊列中帶改變的項目,並從新排版以返回正確值。
解決辦法:
經過延遲訪問佈局信息避免重排版。
總體修改cssText的css代碼,而不是分開訪問,修改cssText的屬性
// 訪問了4次DOM,第二次開始重排列並強迫渲染隊列執行
var el = document.getElementById('div1');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 改進:改變合併,經過cssText實現
var el = document.getElementById('div1');
el.cssText += 'border-left = 1px; border-right = 2px; padding = 5px;';
複製代碼
改變css類名來實現樣式改變
當對DOM元素進行屢次修改時,能夠經過如下的步驟減小重繪和重排版的次數:
(注意:此過程引起兩次重排版,第一次引起一次,第三次引起一次。若是沒有此步驟的話,每次對第二步的改變都有可能帶來重排版。)
從文檔流中摘除該元素,摘除該元素的方法有: a、對其應用多重改變 b、將元素帶回文檔中 c、使其隱藏,進行修改後在顯示 d、使用文檔片斷建立子樹,在將他拷貝進文檔
var doc = document;
// 建立文檔子樹
var frag = doc.createDocumentFragment();
// 自定義函數,將修改內容data賦給文檔片斷frag,具體過程忽略
appendDataToElement(frag,data);
// 注意:添加時實際添加的是文檔片斷的子節點羣,而不是frag本身,只會引起一次重排版
doc.getElementById('div1').appendChild(frag);
複製代碼
建立一個節點的副本,在副本上進行修改,再讓複製節點覆蓋原先節點
// 建立一個節點的副本,在副本上進行修改,再讓複製節點覆蓋原先節點
var oldNode = document.getElementById('old');
var clone = old.cloneNode();
appendDataToElement(clone, data);
oldNode.replaceChild(clone, oldNode);
複製代碼
ps:推薦第二種,由於其涉及最少數量的操做和重排列。
1四、減小對佈局信息的查詢次數,查詢時將他賦值給局部變量參與計算。
例子,在元素網右下方不斷平移時,在timeout中能夠寫:
var current = myElement.offsetLeft;
current++;
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current > 500) {
// stop animation
}
// 拒絕下面的寫法,每次移動都會查詢一次偏移量,致使瀏覽器刷新渲染隊列,很是耗時
myElement.style.left = myElement.offsetLeft + 'px';
myElement.style.top = myElement.offsetLeft + 'px';
if (myElement.offsetLeft > 500) {
// stop animation
}
複製代碼
1五、大量的元素使用:hover以後,頁面性能將下降,特別是IE8中。所以強烈建議,在數據量很大的表格中,減小鼠標在表上移動效果,減小高亮行的顯示,使用高亮是個慢速過程CPU使用率會提升到80%-90%,儘可能避免使用這種效果。
1六、事件託管
講到事件託管,首先咱們來看一看冒泡機制:
DOM2級事件規定事件包括三個階段: ① 事件捕獲階段 ② 處於目標階段 ③ 事件冒泡階段
圖片引用來源:www.w3.org/TR/DOM-Leve…
以下圖的實驗結果能夠知道,當咱們點擊了inner以後,捕獲和冒泡結果如上圖的規律相同;
所以,由於每個元素有一個或多個事件句柄與之相連時,可能會影響性能,畢竟鏈接每個句柄都是有代價的,因此咱們採用事件託管技術,在一個包裝元素上掛接一個句柄,用於處理子元素髮生的全部事件。
下面咱們以以下的dom結構爲例:
假若有一個ul,下面有不少個li:
<div>
<ul id="ulList">
<li id="item1"></li>
<li id="item2"></li>
<li id="item3"></li>
<li id="item4"></li>
<li id="item5"></li>
</ul>
</div>
複製代碼
當某個Li被點擊的時候須要觸發相應的處理事件。咱們一般的寫法,是爲每一個Li都添加onClick的事件監聽。
function addListenersLi(liNode) {
liNode.onclick = function clickHandler(){}
}
window.onload = function(){
var ulNode = document.getElementById("ulList");
var liNodes = ulNode.getElementByTagName("li");
for(var i=0, l = liNodes.length; i < l; i++){
addListeners4Li(liNodes[i]);
}
}
複製代碼
若是li足夠多,或者對於li的操做特別頻繁,爲每個li綁定一個點擊事件將會特別影響性能,由於在此期間,你須要訪問和修改更多的DOM節點,事件的綁定過程發生在onload事件中,綁定自己也很是耗時;同時,瀏覽器須要保存每一個句柄的記錄,很佔用內存。重點是有些綁定了還不必定會用着,並非100%的按鈕或連接都會被點到的喲!
所以,採用事件託管更爲高效,當事件被拋到更上層的父節點的時候,咱們經過檢查事件的目標對象(target)來判斷並獲取事件源Li。下面的代碼能夠完成咱們想要的效果:
var oul = document.getElementById('ulList');
oul.addEventListener('click',function(e){
var e = e || window.event;
var target = e.target || e.srcElement;
console.log(target.nodeName);
if (target.nodeName == 'LI') {
// 事件真正的處理程序
alert(target.id);
e.preventDefault();
e.stopPropagation();
}
else {
console.log(target.nodeName);
}
});
複製代碼