網(wǎng)站性能是創(chuàng)建網(wǎng)頁(yè)或應(yīng)用程序時(shí)最重要的一個(gè)方面。沒(méi)有人想要應(yīng)用程序崩潰或者網(wǎng)頁(yè)無(wú)法加載,或者用戶的等待時(shí)間過(guò)長(zhǎng)。可以從日常的業(yè)務(wù)邏輯出發(fā),去優(yōu)化和提升網(wǎng)站的應(yīng)用性能,特別是JavaScript性能優(yōu)化。
1:垃圾收集
日常中的某些情況下垃圾收集器無(wú)法回收無(wú)用變量,導(dǎo)致的一個(gè)結(jié)果就是——內(nèi)存使用率不斷增高,以下為對(duì)應(yīng)的情況以及處理方法。
①對(duì)象相互引用會(huì)導(dǎo)致引用計(jì)數(shù)始終為2,所以用完對(duì)象后應(yīng)將引用設(shè)為null,例子如下
let element = document.getElementById("test"); let myObject = new Object(); myObject.element = element; element.someObject = myObject; //....用完后需要加如下代碼 myObject.element = null; element.someObject = null;
②當(dāng)數(shù)據(jù)不再有用時(shí),需要通過(guò)將值設(shè)為null來(lái)解除引用,該做法適用于大多數(shù)全局變量和全局對(duì)象屬性,例子如下
function createPerson(name){ let localPerson = new Object(); localPerson.name = name; return localPerson } let globalPerson = createPerson("test") //...用完后手動(dòng)解除 globalPerson = null
③關(guān)于與閉包相關(guān)的內(nèi)存泄漏如下
function assignHandler(){ let element = document.getElementById("test"); element.onclick = function(){ alert(element.id) } } //以上會(huì)導(dǎo)致element的引用數(shù)無(wú)法被回收,更改如下 function assignHandler(){ let element = document.getElementById("test"); let id = element.id; element.onclick = function(){ alert(id) } element = null; }
2:事件委托
在js中,添加到頁(yè)面上的事件處理程序數(shù)量會(huì)直接關(guān)系到頁(yè)面整體運(yùn)行運(yùn)行性能。導(dǎo)致這一問(wèn)題的原因是多方面的。首先函數(shù)都是對(duì)象,都會(huì)占用內(nèi)存;內(nèi)存中對(duì)象越多,性能就越差。其次,必須事先指定所有事件處理程序而導(dǎo)致的DOM訪問(wèn)次數(shù),會(huì)延遲整個(gè)頁(yè)面的交互就緒時(shí)間。以下為對(duì)應(yīng)的情況以及處理方法
①同類型的事件處理函數(shù)過(guò)多時(shí),應(yīng)該結(jié)合為一個(gè),例子如下:
//html代碼 ul id="myLinks" li id="goSomeWhere">Go somewhereSay hi//分別加上事件處理-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"); } //改善點(diǎn)即將click事件結(jié)合在一起 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; } }
②內(nèi)存留有過(guò)時(shí)不用的“空事件處理程序”也是造成性能問(wèn)題的主因,兩種情況下會(huì)造成該問(wèn)題。運(yùn)用removeChild()和replaceChild()方法去除節(jié)點(diǎn)時(shí);在使用innerHTML替換頁(yè)面某一部分時(shí),如果帶有事件處理程序的元素被innerHTML刪除了,那么原有事件處理函數(shù)極有可能無(wú)法被回收,例子如下
//例子中id為myBtn的點(diǎn)擊事件變?yōu)榱丝帐录幚沓绦?/span> 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"; }; //改善點(diǎn)即需要手工移除事件處理程序 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:注意作用域
關(guān)于作用域鏈,我們明白訪問(wèn)全局變量會(huì)比訪問(wèn)局部變量要慢
①若某處循環(huán)使用全局變量時(shí),我們可以略做修改,例子如下
//假設(shè)有多個(gè)img標(biāo)簽的內(nèi)容,循環(huán)中引用了多次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"; } //改善點(diǎn) 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,因?yàn)閣ith會(huì)增加其中執(zhí)行代碼的作用域鏈的長(zhǎng)度
4:選擇正確方法
首先,我們要了解JS中算法的復(fù)雜度
| 標(biāo)記名稱 | 描述 | |
| O(1) | 常數(shù) | 不管有多少值,執(zhí)行的時(shí)間都是恒定的。一般表示簡(jiǎn)單值和存儲(chǔ)在變量中的值 |
| O(log n) | 對(duì)數(shù) | 總的執(zhí)行時(shí)間和值的數(shù)量相關(guān),但是要完成算法并不一定要獲取每個(gè)值。例如:二分查詢 |
| O(n) | 線性 | 總執(zhí)行時(shí)間和值的數(shù)量直接相關(guān)。例如:遍歷某個(gè)數(shù)組中的所有元素 |
| O(n^2) | 平方 | 總執(zhí)行時(shí)間和值的數(shù)量有關(guān),每個(gè)值至少要獲取n次。例如:插入排序 |
常數(shù)值和訪問(wèn)數(shù)組元素操作都是O(1)操作;對(duì)象屬性查找操作是O(n)操作;
如let values = [5, 10]; let sum = values[0] + values[1]屬于O(1)操作;let values = window.location.href屬于O(2)操作
①遇到有多次屬性查詢的場(chǎng)合,可以考慮是否能做優(yōu)化,例子如下
//這里總共做了6次屬性查詢,其中window.location.href.substring與window.location.href.indexOf分別為3次 let query = window.location.href.subsring(window.location.href.indexOf("?")) //改善, 第一次訪問(wèn)時(shí)復(fù)雜度會(huì)是O(n),但該版本只有4次屬性查詢,相對(duì)于原始版本節(jié)省了33% let url = window.location.href; let query = url.substring(url.indexOf("?"));
②循環(huán)優(yōu)化,這里其實(shí)用后測(cè)試循環(huán)代替前測(cè)試循環(huán)會(huì)更好,不過(guò)本地不采用,例子如下
//原有復(fù)雜度為O(n) for (let i = 0; i < values.length; ++i){ process(values[i]); } //更改后復(fù)雜度為O(1) for (let i = values.length - 1; i >= 0; --i){ process(values[i]) }
③最小化語(yǔ)句數(shù)相關(guān)
例如進(jìn)行多個(gè)聲明時(shí),我們可以進(jìn)行組合,例子如下
//多個(gè)聲明 let count = 5; let color = "blue"; let values = [1, 2, 3]; //組合成一個(gè) let count = 5, color = ”blue", values = [1, 2, 3]
例如插入迭代值時(shí),例子如下
//修改前 let name = values[i]; i++; //修改后 let name = values[i++]
使用數(shù)組和對(duì)象字面量時(shí),例子如下
//修改前 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, }
④創(chuàng)建DOM節(jié)點(diǎn)最好使用innerHTML方法,因?yàn)閕nnerHTML設(shè)置值時(shí),后臺(tái)會(huì)創(chuàng)建HTML解析器,然后使用內(nèi)部的DOM調(diào)用來(lái)創(chuàng)建DOM結(jié)構(gòu),而非基于JS的DOM調(diào)用。
調(diào)用一次innerHTML,就會(huì)進(jìn)行一次現(xiàn)場(chǎng)刷新,循環(huán)插入DOM結(jié)構(gòu)時(shí),應(yīng)注意盡量調(diào)用少次數(shù)的innerHTML,代碼如下
//錯(cuò)誤方法,做了很多次現(xiàn)場(chǎng)刷新 let list = document.getElementById("myList"), i; for (i = 0; i < 10; ++i){ list.innerHTML = html+= "
⑤其他如有多個(gè)if-else語(yǔ)句時(shí),應(yīng)盡可能轉(zhuǎn)為Switch語(yǔ)句;用appendChild()插入元素時(shí),應(yīng)采用自上而下插入;面向?qū)ο缶幊虝r(shí),應(yīng)合理釋放內(nèi)存,設(shè)object為null。
掃一掃 加微信咨詢