在 jQuery3.0中,buildFragment 是一個私有函數,用來構建一個包含子節點 fragment 對象。這個 fragment 在 DOM1 中就已經有了,所有瀏覽器都支持。當頻繁操作(添加、插入) DOM 時使用該方法可以提高性能,John resig 做過一個測試及一篇博客。 jQ ...
在 jQuery3.0中,buildFragment 是一個私有函數,用來構建一個包含子節點 fragment 對象。這個 fragment 在 DOM1 中就已經有了,所有瀏覽器都支持。當頻繁操作(添加、插入) DOM 時使用該方法可以提高性能,John resig 做過一個測試及一篇博客。
jQuery3.0 中 buildFragment 只在 domManip 和 jQuery.parseHTML 中使用,domManip 則被 DOM 操作如 append、prepend、before、after 等方法的所依賴。如下圖
buildFragment 函數有 5 個參數,源碼如下
function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, contains, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( jQuery.type( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // Convert html into DOM nodes } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // Deserialize a standard representation tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } } // Remove wrapper from fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // Skip elements already in the context collection (trac-4087) if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } contains = jQuery.contains( elem.ownerDocument, elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( contains ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; }
該方法主要執行步驟
- 通過第二個參數 content 創建 fragment
- 通過第一個參數 elems 構建 nodes ,將 elems 內元素轉成 DOM 元素存放於數組 nodes 中
- 將 nodes 里元素迴圈放入添加到文檔碎片 fragment 上
- 返回 fragment
重點在第 2 步,構建 nodes,有 3 種情形
- elem 是 DOM 元素(根據nodeType判斷),直接放入 nodes 數組中
- elem 是字元串且不是 HTML tag,創建文本節點對象(textNode),放入 nodes 數組中
- elem 是字元串且是 HTML tag,將其轉成 DOM 元素,放入 nodes 數組中
如圖示
後面的兩個參數需要註意下
1. 最後兩個參數 selection 和 ignored 只在 replaceWith 方法里使用。需要瞭解的是 replaceWith 只做節點替換,不會替換先前元素的所有數據(Data),比如綁定事件,$.data 都不會被新元素擁有。
2. scripts 參數只在 jQuery.parseHTML 方法里使用(domManip里傳false),當 jQuery.parseHTML 的第三個參數 keepScripts 為 false 時將刪除節點里所有的 script tag
buildFragment 在 jQuery 各個版本中的演變
- 1.0.x ~ 1.3.x 中沒有 buildFragment 函數,即沒有抽取出該函數為 domManip 服務
- 1.4.x 中首次引入 buildFragment ,當時是掛在 jQuery 上的靜態方法,有三個參數 args, nodes, scripts。一直到2.x.x 依然是公開可以訪問的
- 3.x.x 開始 buildFragment 變成了一個私有函數,只能在 jQuery 代碼內部訪問,客戶端程式員無法訪問