讀書筆記(03) - 性能 - JavaScript高級程序設計

coding

作用域鏈查找

作用域鏈的查找是逐層向上查找。查找的層次越多,速度越慢。隨着硬件性能的提升和瀏覽器引擎的優化,這個慢我們基本可以忽略。

除了層級查找損耗的問題,變量的修改應只在局部環境進行,儘量避免在局部環境下去操作修改父級變量的值。(react/vue 單向數據流的數據傳輸方式)

優化方法:聲明一個變量存儲引用(該方法應用甚多)

不必要的屬性查找

// 未優化(window.location.href 3*2 6次)
var query = window.location.href.substring(window.location.href.indexOf('?');

// 優化後(3次,以後多次調用url,查詢次數不會增加)
var url = window.location.href;
var query = url.substring(url.indexOf('?')
url = null

window.location.href

函數裏面聲明的變量,在函數調用棧執行後退出時,會自動清除引用。而全局變量和閉包則會與之相反,繼續保存,所以使用用後需手動標記清除,以免造成內存泄漏。

優化循環

  1. 減值迭代
  2. 簡化終止條件
  3. 簡化循環體
  4. 使用後測試循環

減值迭代

日常應用不多,與增值迭代的區別,就在i存儲的值。減值迭代i的值不斷在變小,存儲的空間也在變小。

但在前端極少需要遍歷上萬次上億次的數據,上千上百都很少,所以這個優化可忽略。而且我們遍歷的順序一般都是從數組頭部開始,所以增值迭代應用的更多。

// 增值迭代(用的較多)
for(var i = 0; i < len; i++) {
    //...
}

// 減值迭代
for(var i = len - 1; i >= 0 ; i--) {
    //...
}

簡化終止條件 (常用)

終止條件應該是一個固定值判斷,應避免在終止條件上做其他運算(屬性查找等)。

// 未優化,每次循環都會去計算數組長度
var arr = ['HTML', 'CSS', 'JavaScript'];
for (var i = 0; i < arr.length; i++) {
    //...
}

// 優化後
for (var i = 0, len = arr.length; i < len; i++) {
    //...
}
// 聲明瞭一個變量len用於儲存數組長度,只會計算一次

簡化循環體

循環體的代碼應該着重於只需要遍歷處理的代碼,其他無關代碼應放置到循環體外面。

後測試循環

最常用的for循環和while循環都是前測試循環。而do-while這種後測試循環,可以避免最初終止條件的計算,因此運行更快。

前測試循環(for/while),可能一次都不會執行循環體

後測試循環(do...while),至少執行一次

用確定索引值更快

// for循環遍歷
var arr = ['HTML', 'CSS', 'JavaScript'];
for (let i = 0, len = arr.length; i < len; i++) {
    arr[i];
}

// 確定索引值
arr[0]; 
arr[1]; 
arr[2];

其他

  1. 原生方法較快(Math)
  2. switch語句較快 (多個if情況下)
  3. 位運算符較快

TIPS: 判斷優化,最可能的到最不可能的順序組織(if/switch)

最小語句數

符合 write less, do more 的代碼追求

多個變量聲明合併

// 多個var聲明
var name = 'KenTsang';
var age = 28;
var job = 'Developer';

// 合併一個var聲明
var name = 'KenTsang',
    age = 27,
    job = 'Developer';

插入迭代值

// 優化前
var name = value[i];
i++;

// 優化後
var name = value[i++];

數組/對象字面量

創建引用類型可以使用構造函數和字面量兩種方式,不過日常習慣都使用字面量,因爲語句更簡潔,寫起來更像數據封裝。

// 字面量
var arr = [1, 2, 3, 4];
var obj = {
    name: 'KenTsang'
}

// 構造函數
var arr = new Array(1, 2, 3, 4);
var obj = new Object();
obj.name = 'KenTsang';

DOM優化交互

最小現場更新

現場更新:一旦你需要訪問的 DOM 部分是已經顯示的頁面的一部分,那麼你就是在進行一個現場更新

文檔片段

文檔片段相當一個臨時的佔位符,只有片段中的內容會被添加到DOM上,片段本身並不會被添加。

// 代碼片段標籤
var ele  = document.getElementById('ul');
var fragment = document.createDocumentFragment();
var browsers = ['Firefox', 'Chrome', 'Opera', 
    'Safari', 'IE'];

browsers.forEach(function(browser) {
    var li = document.createElement('li');
    li.textContent = browser;
    fragment.appendChild(li);
});

// 只會操作一次DOM
ele.appendChild(fragment);

innerHTML

合併插入代碼一次性設置innerHTML。

// 優化前:操作多次DOM
var list = document.getElementById("myList");
for (var i=0; i < 10; i++) {
    list.innerHTML += "<li>Item " + i + "</li>";
}

// 優化後:操作一次DOM
var innerHtml = '';
for (var i = 0; i < 10; i++) {
    innerHtml += '<li>Item' + i + '</li>';
}
list.innerHTML = innerHtml;

事件代理(事件委託)

通過事件流——冒泡機制實現代理,子元素事件觸發冒泡到父元素,由父元素綁定一個事件進行統一處理,避免多個事件綁定影響性能。

事件冒泡

<ul class="list">
    <li class="item">HTML</li>
    <li class="item">CSS</li>
    <li class="item">JavaScript</li>
</ul>

var listEle = document.getElementById('list');

listEle.addEventListener('click', function(event) {
    if (event.target.className.indexOf('item') > -1) {
        console.log(event.target.innerHTML); 
    }
})

// jquery
$('#list').on('click', '.item', function(event){
    console.log($(this).html());
})

注意HTMLCollection

任何時候要訪問 HTMLCollection,不管它是一個屬性還是一個方法,都是在文檔上進行一個查詢,這個查詢開銷很昂貴。
// 一個死循環例子
<a href="">link</a>
    
var existLinkEle = document.getElementsByTagName('a');
for (var i = 0; i < existLinkEle.length; i++) {     
    console.log(i);
    var linkEle = document.createElement('a');
    document.body.appendChild(linkEle);
}
// body會不斷地插入a標籤

因爲existLinkEle.length每次循環都會重新計算頁面a節點的數量,而得到的值一直遞增。

// 優化(一個變量存儲引用)
var len = existLinkEle.length;
for (var i = 0; i < len; i++) {
    //...
}

返回HTMLCollection對象情況有:

  1. document.getElementByTagName()
  2. 獲取元素的childNodes屬性
  3. 獲取元素的attributes屬性
  4. document.forms,document.images

參考文檔

作者:以樂之名
本文原創,有不當的地方歡迎指出。轉載請指明出處。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章