場景
異步回調,解析HTML,過濾出某一部分,加載到頁面。
我的代碼
html = $(html) ;
追蹤
經查: jQuery在解析Html時,會有內存泄露。追蹤的執行代碼如下:
1. init:
if (typeof selector === "string")
...
ret = jQuery.buildFragment([match[1]], [doc]);
2. buildFragment
jQuery.clean(args, doc, fragment, scripts);
3.clean 這才是核心,還不明白為什么起這個名字。 分析文章: http://www.cnblogs.com/nuysoft/archive/2012/01/11/2318651.html
div = context.createElement("div"), // 重點關注對象 .
safeChildNodes = safeFragment.childNodes, //safeFragment 是在加載完jQuery之后執行的。 它只是進行了創建,並沒有追加到 DOM 中。
...
// Append wrapper element to unknown element safe doc fragment
if (context === document) {
// Use the fragment we've already created for this document
safeFragment.appendChild(div); //把div添加到 safeFragment,作用何在?
} else {
// Use a fragment created with the owner document
createSafeFragment(context).appendChild(div);
}
...
div.innerHTML = wrap[1] + elem + wrap[2]; //內存開始增長。之后並沒有回落,懷疑是清理有問題。
...
div.parentNode.removeChild(div); //很明顯,作者想到了要清理,但根據監測結果,內存並沒有回落。在HTML內容很大的情況下,觀察效果明顯。
解決方案
作者的方案是,創建div,把它添加到 Fragment,再設置 innerHTML,和網上流傳的
url: http://archive.cnblogs.com/a/2260680/
出現這種內存泄漏需要有三個條件:
1. 內存中存在一個未加入DOM樹的元素
2. 給這個元素設置innerHTML,注意,必須是能創建元素並且綁定了DOM 0級事件
3. 必須在這個元素加入DOM樹前設置它的innerHTML
思想如出一轍,但有不同。
把我的代碼按網上方案進行改造。
var div = document.createElement("div");
document.body.appendChild(div) ;
div.innerHTML = html ;
html = $(div.childNodes );
...
document.body.removeChild(div);
使用上述方法后,發現IE下設置 innerHTML 后 childNodes 是一坨 html 代碼,並沒有解析成對象。調試jQuery代碼發現,jQuery解析時,執行了如下代碼:
1. 在 init 里:
match = quickExpr.exec(selector);
2. 在 clean 函數 時:
elem = elem.replace(rxhtmlTag, "<$1></$2>");
而且,它在設置 innerHtml 時,進行發如下操作:
div.innerHTML = wrap[1] + elem + wrap[2];
綜合jQuery寫法,修改如下:
html = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/.exec(html) ; html = html[1].replace(/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, "<$1></$2>") ; var div = document.createElement("div"); document.body.appendChild(div) ; div.innerHTML = "div<div>" + html +"</div>"; html = $(div.lastChild.childNodes); ... document.body.removeChild(div);
使用 Drip,進行測試,使用 Show Memory Usage 查看,內存增加非常小,使用 Show Dom Usage 查看,曲線還是很明顯(但點Show Dom Leaks,頁面會變白)
使用 sIEve 進行測試,使用 Show Memory Usage 查看,內存增長曲線非常小。使用 Show Dom Usage 查看,曲線基本持平。
使用 任務管理器進行查看,發現內存可以回落。 sIEve 比 Drip 要准確。
下圖是 sIEve 的兩個曲線圖:
效果還是不錯的。
后記:
jQuery1.7.2 在 IE8 下與 frameset 有嚴重的內存泄露。
這只是在單頁面里進行測試,沒有問題。在 frameset 里,測試,刷新一下子頁面,或切換左邊的菜單,都會引起內存泄露。
環境 jQuery 1.7.2, IE8 , 使用 frameset。
現象是刷新子頁面所有元素都泄露。包括 最外層的 frameset。
經排查, 改為 jQuery 1.7.1 ,問題解決。