作用域鏈查找
作用域鏈的查找是逐層向上查找。查找的層次越多,速度越慢。隨着硬件性能的提升和瀏覽器引擎的優化,這個慢我們基本可以忽略。
除了層級查找損耗的問題,變量的修改應只在局部環境進行,儘量避免在局部環境下去操作修改父級變量的值。(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
函數裏面聲明的變量,在函數調用棧執行後退出時,會自動清除引用。而全局變量和閉包則會與之相反,繼續保存,所以使用用後需手動標記清除,以免造成內存泄漏。
優化循環
- 減值迭代
- 簡化終止條件
- 簡化循環體
- 使用後測試循環
減值迭代
日常應用不多,與增值迭代的區別,就在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];
其他
- 原生方法較快(Math)
- switch語句較快 (多個if情況下)
- 位運算符較快
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
對象情況有:
-
document.getElementByTagName()
。 - 獲取元素的
childNodes
屬性 - 獲取元素的
attributes
屬性 -
document.forms
,document.images
等
參考文檔
作者:以樂之名
本文原創,有不當的地方歡迎指出。轉載請指明出處。