前端程式越搞越複雜,接踵而至的問題越來越多,雖然瀏覽器的效能不斷地提昇,但是只要IE6沒被淘汰的一天,網頁程式設計師永遠要替IE6補洞,Memory Leak是一個非常難搞得議題。關於微軟的官方說法可以參考Understanding and Solving Internet Explorer Leak Patterns,裡頭有詳細說明IE6會如何發生memory leak的情況。發生memory leak的徵狀就是:記憶體不斷飆上去,就算reload頁面,記憶體也不會被釋放,除非關掉瀏覽器。
在此不介紹為何為引發memory leak,因為我覺得這是瀏覽器應該要處理的問題,要拿出來講只是因為IE6沒辦法處理好罷了,雖然其他瀏覽器也並不是沒有這個問題,但是至少問題都沒有這麼嚴重,因此只需要針對IE6來處理即可。以下是我整理出幾種比較好的處理方式:
- Douglas Crockford針對memory leak的議題發表一篇文章:JScript Memory Leaks提出一個purge的function,概念非常簡單,就是遍歷元素底下的所有節點,將所有的function設為null(這屬於closure的循環參照索引發的memory leak)。一般來說可以在unload時或者你要removeNode之前來做這個動作,例如:12345678910111213141516171819202122function purge(d) {var a = d.attributes, i, l, n;if (a) {l = a.length;for (i = 0; i < l; i += 1) {n = a[i].name;if (typeof d[n] === 'function') {d[n] = null;}}}a = d.childNodes;if (a) {l = a.length;for (i = 0; i < l; i += 1) {purge(d.childNodes[i]);}}}window.onunload = function(){purge(document.body);}
- Event Cache :若你的專案沒用到一些framework時,可能會用到一些自訂的addEvent方法,這時可以透過EventCache.add來將事件加入cache,然後在unload時做EventCache.flush。這樣可以避免在unload時循環整個body,雖然降低了memory leak,但卻吃掉cpu的效能。123456789101112131415161718192021222324252627var EventCache = function () {var listEvents = [];return {listEvents: listEvents,add: function (node, sEventName, fHandler, bCapture) {listEvents.push(arguments);},flush: function () {var i, item;for (i = listEvents.length - 1; i & gt; = 0; i = i - 1) {item = listEvents[i];if (item[0].removeEventListener) {item[0].removeEventListener(item[1], item[2], item[3]);};/* From this point on we need the event names to be prefixed with 'on" */if (item[1].substring(0, 2) != & quot; on & quot;) {item[1] = & quot;on & quot; + item[1];};if (item[0].detachEvent) {item[0].detachEvent(item[1], item[2]);};item[0][item[1]] = null;};}};}();
- 利用try…finally來清除不被引用的參考:這常會發生在我們利用function來creat Element時,忽略了不再被引用的參考,然後直接return,在IE6中會引發memory leak,這篇文章透過try,finally來清除不被引用的參考,例如:12345678910111213141516function createButton() {var obj = document.createElement("button");obj.innerHTML = "click me";obj.onclick = function() {//handle onclick}obj.onmouseover = function() {//handle onmouseover}//this helps to fix the memory leak issuetry {return obj;} finally {obj = null;}}
- innerHTML所引發的memory leak:這個議題最為常見,innerHTML不被W3C列為標準,但因為他比起操作DOM元素容易的多了,因此廣泛的被使用,但是之前有提到,最好是在removeNode時做一次檢查,將node裡頭的function全部設為null,以避免closure所引發的memory leak,但是innerHTML實在太方便了,讓所有programer將這個問題拋在腦後,因為我隨時可以設定某個元素的innerHTML,哪怕他裡頭又肥又大,一句innerHTML就可以改變DOM元素。文章裡頭的例子用YUI來做示範,不過事實上,他還是用了Douglas了perge來清除循環引用。12345678910111213141516171819202122232425262728293031323334YAHOO.util.Dom.setInnerHTML = function (el, html) {el = YAHOO.util.Dom.get(el);if (!el || typeof html !== 'string') {return null;}// Break circular references.(function (o) {var a = o.attributes, i, l, n, c;if (a) {l = a.length;for (i = 0; i < l; i += 1) {n = a[i].name;if (typeof o[n] === 'function') {o[n] = null;}}}a = o.childNodes;if (a) {l = a.length;for (i = 0; i < l; i += 1) {c = o.childNodes[i];// Purge child nodes.arguments.callee(c);// Removes all listeners attached to the element via YUI's addListener.YAHOO.util.Event.purgeElement(c);}}})(el);// Remove scripts from HTML string, and set innerHTML propertyel.innerHTML = html.replace(/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig, "");// Return a reference to the first childreturn el.firstChild;};
大概是這樣了,memory leak實在是個很惱人的議題,不過有責任感的前端工程是還是要將之視為己任的,畢竟項目越做越大,然而記憶體越吃越兇,這是很可怕的一件事。