首先,性能優化分好幾個方面,本章我們從js方面來優化。
1:垃圾收集
日常中的某些情況下垃圾收集器無法回收無用變量,導致的一個結果就是——內存使用率不斷增高,以下為對應的情況以及處理方法。
①對象相互引用會導致引用計數始終為2,所以用完對象后應將引用設為null,例子如下
let element = document.getElementById("test"); let myObject = new Object(); myObject.element = element; element.someObject = myObject;
//....用完后需要加如下代碼
myObject.element = null;
element.someObject = null;
②當數據不再有用時,需要通過將值設為null來解除引用,該做法適用於大多數全局變量和全局對象屬性,例子如下
function createPerson(name){ let localPerson = new Object(); localPerson.name = name; return localPerson } let globalPerson = createPerson("test") //...用完后手動解除 globalPerson = null
③關於與閉包相關的內存泄漏如下
function assignHandler(){ let element = document.getElementById("test"); element.onclick = function(){ alert(element.id) } } //以上會導致element的引用數無法被回收,更改如下 function assignHandler(){ let element = document.getElementById("test"); let id = element.id; element.onclick = function(){ alert(id) } element = null; }
2:事件委托
在js中,添加到頁面上的事件處理程序數量會直接關系到頁面整體運行運行性能。導致這一問題的原因是多方面的。首先函數都是對象,都會占用內存;內存中對象越多,性能就越差。其次,必須事先指定所有事件處理程序而導致的DOM訪問次數,會延遲整個頁面的交互就緒時間。以下為對應的情況以及處理方法
①同類型的事件處理函數過多時,應該結合為一個,例子如下:
//html代碼 <ul id="myLinks"> <li id="goSomeWhere">Go somewhere</li> <li id="sayHi">Say hi</hi> </ul> //分別加上事件處理-JS代碼 let item1 = document.getElementById("goSomeWhere"); let item2 = document.getElementById("sayHi"); EventUtil.addHandler(item1, "click", function(event){ console.log("goSomeWhere") } EventUtil.addHandler(item2, "click", function(event){ console.log("sayHi"); } //改善點即將click事件結合在一起 let list = document.getElementById("myLinks") EventUtil.addHandler(list, "click", function(event){ event = EventUtil.getEvent(event); let target = EventUtil.getTarget(event); switch(target.id){ case "goSomeWhere": console.log("goSomeWhere"); break; case "sayHi": console.log("sayHi"); break; } }
②內存留有過時不用的“空事件處理程序”也是造成性能問題的主因,兩種情況下會造成該問題。運用removeChild()和replaceChild()方法去除節點時;在使用innerHTML替換頁面某一部分時,如果帶有事件處理程序的元素被innerHTML刪除了,那么原有事件處理函數極有可能無法被回收,例子如下
//例子中id為myBtn的點擊事件變為了空事件處理程序 <div id="myDiv"> <input type="button" value="Click Me" id="myBtn"> </div> <script type="text/javascript"> let btn = document.getElementById("myBtn"); btn.onclick = function(){ document.getElementById("myDiv").innerHTML = "xxxx"; }; </script> //改善點即需要手工移除事件處理程序 <div id="myDiv"> <input type="button" value="Click Me" id="myBtn"> </div> <script type="text/javascript"> let btn = document.getElementById("myBtn"); btn.onclick = function(){ btn.onclick = null; document.getElementById("myDiv").innerHTML = "xxxx"; }; </script>
3:注意作用域
關於作用域鏈,我們明白訪問全局變量會比訪問局部變量要慢
①若某處循環使用全局變量時,我們可以略做修改,例子如下
//假設有多個img標簽的內容,循環中引用了多次document全局變量 function updateUI(){ let imgs = document.getElementsByTagName("img") for (let i = 0; len = imgs.length; i < len; ++i){ imgs[i].title = document.title + " image “ + i } let msg = document.getElementById("msg"); msg.innerHTML = "Update"; } //改善點 function updateUI(){ let doc = document let imgs = doc.getElementsByTagName("img") for (let i = 0; len = imgs.length; i < len; ++i){ imgs[i].title = doc.title + " image “ + i } let msg = doc.getElementById("msg"); msg.innerHTML = "Update"; }
②盡量少用with,因為with會增加其中執行代碼的作用域鏈的長度
4:選擇正確方法
首先,我們要了解JS中算法的復雜度
標記名稱 | 描述 | |
O(1) | 常數 | 不管有多少值,執行的時間都是恆定的。一般表示簡單值和存儲在變量中的值 |
O(log n) | 對數 | 總的執行時間和值的數量相關,但是要完成算法並不一定要獲取每個值。例如:二分查詢 |
O(n) | 線性 | 總執行時間和值的數量直接相關。例如:遍歷某個數組中的所有元素 |
O(n^2) | 平方 | 總執行時間和值的數量有關,每個值至少要獲取n次。例如:插入排序 |
常數值和訪問數組元素操作都是O(1)操作;對象屬性查找操作是O(n)操作;
如let values = [5, 10]; let sum = values[0] + values[1]屬於O(1)操作;let values = window.location.href屬於O(2)操作
①遇到有多次屬性查詢的場合,可以考慮是否能做優化,例子如下
//這里總共做了6次屬性查詢,其中window.location.href.substring與window.location.href.indexOf分別為3次 let query = window.location.href.subsring(window.location.href.indexOf("?")) //改善, 第一次訪問時復雜度會是O(n),但該版本只有4次屬性查詢,相對於原始版本節省了33% let url = window.location.href; let query = url.substring(url.indexOf("?"));
②循環優化,這里其實用后測試循環代替前測試循環會更好,不過本地不采用,例子如下
//原有復雜度為O(n) for (let i = 0; i < values.length; ++i){ process(values[i]); } //更改后復雜度為O(1) for (let i = values.length - 1; i >= 0; --i){ process(values[i]) }
③最小化語句數相關
例如進行多個聲明時,我們可以進行組合,例子如下
//多個聲明 let count = 5; let color = "blue"; let values = [1, 2, 3]; //組合成一個 let count = 5, color = ”blue", values = [1, 2, 3]
例如插入迭代值時,例子如下
//修改前 let name = values[i]; i++; //修改后 let name = values[i++]
使用數組和對象字面量時,例子如下
//修改前 let values = new Array(); values[0] = 123; values[1] = 456; values[2] = 789; let person = new Object(); person.name = "Eric"; person.age = 20; //修改后 let values = [123, 456, 789] let person = { name: "Eric", age:20, }
④創建DOM節點最好使用innerHTML方法,因為innerHTML設置值時,后台會創建HTML解析器,然后使用內部的DOM調用來創建DOM結構,而非基於JS的DOM調用。
調用一次innerHTML,就會進行一次現場刷新,循環插入DOM結構時,應注意盡量調用少次數的innerHTML,代碼如下
//錯誤方法,做了很多次現場刷新 let list = document.getElementById("myList"), i; for (i = 0; i < 10; ++i){ list.innerHTML = html+= "<li>Item " + i + "</li>" } //正確方法,盡管在字符串連接上有性能損失,但卻只做了一次現場刷新 let list = document.getElementById("myList"), html = "", i; for (i = 0; i < 10; ++i){ html += "<li>Item " + i + "</li>" } list.innerHTML = html
⑤其他如有多個if-else語句時,應盡可能轉為Switch語句;用appendChild()插入元素時,應采用自上而下插入;面向對象編程時,應合理釋放內存,設object為null。