載入和執行
1、 </body>
閉合標籤之前,將所有的<script>
標籤放在頁面底部,確保在腳步執行之前頁面已經完成渲染。
2、 合併指令碼。下載單個 100KB 的檔案將比下載 4 個 25KB 的檔案更快,因此頁面標籤的<script>
標籤越少,載入也就越快,響應也越迅速。無論外鏈檔案或者內嵌指令碼。
3、 使用無阻塞下載 javascript
的方法,即在頁面載入完成後才載入 Javascript
程式碼,以下有幾種無阻塞下載方法:
-
使用
<script>
的defer
屬性(只有 IE4+和 FireFox3.5+支援)<script src="file.js" defer></script> 複製程式碼
-
使用動態建立的
<script>
元素來下載並執行程式碼// 通過監聽script元素接收完成事件來獲得指令碼載入完成時的狀態 // 封裝標準和IE特有的實現方法函式 function loadscript(url, callback) { let script = document.createElement('script'); if (script.readyState) { //IE script.onreadystatechange = function() { if (script.readyState === 'loaded' || script.readyState === 'complete') { script.onreadystatechange = null; callback(); } }; } else { // 其他瀏覽器 scripy.onload = function() { callback(); }; } script.src = url; document.getElementsByTagName('head')[0].appendchild(script); } // 使用上述封裝函式載入檔案 loadscript('file.js', function() { console.log('file is loaded ~'); }); 複製程式碼
-
使用 XHR 物件下載
javascript
程式碼並注入頁面中let xhr = new XMLHttpRequest(); xhr.open('get', 'file.js', true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { let script = document.createElement('script'); script.text = xhr.responseText; document.body.appendChild(script); } } }; 複製程式碼
總結:
向頁面中新增大量 JavaScript
的推薦做法:先新增動態載入所需程式碼,然後載入初始化頁面所需剩下程式碼
資料儲存
1、訪問字面量和區域性變數的速度比訪問陣列元素和物件成員快,因此儘量使用字面量和區域性變數,減少陣列項和物件成員的使用。
2、儘可能使用區域性變數。如果某個跨作用域的值在函式中被引用一次以上,把它儲存到區域性變數中。
// 不推薦
function initFn() {
// document.getElementById('myImg')多次被引用
document.getElementById('myImg').src = 'photo.png';
document.getElementById('myImg').alt = 'photo';
}
// 推薦
function initFn() {
// 改寫
let myImg = document.getElementById('myImg');
myImg.src = 'photo.png';
myImg.alt = 'photo';
}
複製程式碼
3、避免使用 with 語句,它會改變執行環境作用域鏈。try-catch 中的 catch 也有同樣的影響,看實際情況中進行運用。
4、屬性和方法在原型鏈中的位置越深,訪問的速度越慢
總結:
把常用的物件成員、陣列元素、跨域變數儲存在區域性變數中
DOM
1、最小化 DOM 訪問次數,如果需要多次訪問某個 DOM 節點,使用區域性變數儲存它的引用。
// 不推薦
function innerHTMLLoop(){
for(let i = 0; i < 5; i++){
document.getElementById('myDiv').innerHTML += 'a';
}
}
// 推薦
function innerHTMLLoop(){
let content = '';
for(let i = 0; i < 5; i++){
content += 'a';
}
document.getElementById('myDiv').innerHTML = content;
}
複製程式碼
2、如果需要在迭代中使用 HTML 集合,把它的長度快取到一個變數中;如果需要多次操作集合,把它拷貝到一個陣列中。
-
HTML 集合有
document.getElementsByName()
,document.getElementsClassName()
,document.getElementsByTagName()
,document.images
,document.links
,document.forms
等等,返回的是類似陣列的列表,但是它以一種“假定實時態”實時存在,即當底層文件物件更新時,它也會自動更新。// 死迴圈 let divs = document.getElementsByTagName('div'); for (let i = 0; i < divs.length; i++) { document.body.appendChild(document.createElement('div')); } 複製程式碼
-
當遍歷一個集合時,把集合儲存在區域性變數中,並把 length 快取在迴圈外部
// 推薦 function collectNodes() { let coll = document.getElementsByTagName('div'); let len = coll.length; let nodeName = ''; let tagName = ''; let el = null; for (let i = 0; i < len; i++) { el = coll[i]; nodeName = el.nodeName; tagName = el.tagName; } return { nodeName, tagName }; } 複製程式碼
3、減少重繪和重排,批量修改樣式時,“離線”操作 DOM
樹,使用快取,並減少訪問佈局資訊的次數。
-
當頁面佈局和幾何屬性改變時需要“重排”。下列情況均會發生“重排”:
1、新增或刪除可見的
DOM
元素2、元素位置發生改變
3、元素尺寸改變(包括:外邊距、內邊距、邊框厚度、寬度、高度等屬性改變)
4、內容改變,如文字改變或圖片被另一個不同尺寸的圖片代替
5、頁面渲染器初始化
6、瀏覽器視窗改變
-
完成“重排”後,瀏覽器會重新繪製受影響的部分到螢幕中,過程為“重繪”。
-
減少“重繪”和“重排”
1、使元素脫離文件流
2、對其應用多次改變
3、把元素帶回文件中
// 第一種方式:隱藏元素,應用修改,重新顯示 let ul = document.getElementById('nyList'); ul.style.display = 'none'; // 向ul中新增附加資料 appendDataToElement(ul, data); ul.style.display = 'block'; // 第二種方式:使用文件片段(document fragment)在當前DOM之外構建一個子樹,再把它拷貝到文件(推薦) let fragment = document.createDocumentFragment(); // 向fragment中新增附加資料 appendDataToElement(ul, data); document.getElementById('myList').appendChild(fragment); // 第三種方式:將原始元素拷貝到一個脫離文件的節點中,修改副本,完成後再替換原始元素 let old = document.getElementById('myList'); let clone = old.cloneNode(true); // 向clone中新增附加資料 appendDataToElement(ul, data); old.parentNode.replaceChild(clone, old); 複製程式碼
4、使用事件委託減少事件處理器的數量
// Event物件提供了一個屬性叫target,可以返回事件的目標節點,我們成為事件源,也就是說,target就可以表示為當前的事件操作的dom,但是不是真正操作dom,當然,這個是有相容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement。
myLi.onclick = function(e) {
let e = e || window.event;
let target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() == 'li') {
alert(target.innerHTML);
}
};
複製程式碼
演算法和流程控制
- 選擇時間複雜度低的演算法
- 避免使用 for-in 迴圈,除非需要遍歷一個屬性數量未知的物件
- 使用 if-else 要確保最可能出現的條件放在首位,條件數量大時,優先使用 switch
- 使用查詢表。查詢表相對於 if-else 和 switch,不近速度快,而且可讀性好
// 將返回值集合存入陣列 let results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,]; // 返回當前結果 return results[value]; 複製程式碼
構建和部署
-
合併 JavaScript 檔案以減少 HTTP 請求數
-
使用 webpack 壓縮 JavaScript 檔案
-
使用 CDN 提供 JavaScript 檔案;CDN 不僅可以提升效能,也會為你管理檔案的壓縮和快取
參考:圖靈圖書《高效能JavaScript》