文檔碎片是什么
http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-B63ED1A3
DocumentFragment is a "lightweight" or "minimal" Document object. It is very common to want to be able to extract a portion of a document's tree or to create a new fragment of a document
參考標准的描述,DocumentFragment是一個輕量級的文檔對象,能夠提取部分文檔的樹或創建一個新的文檔片段
換句話說有文檔緩存的作用
createDocumentFragment有什么作用
多次使用節點方法(如:appendChild)繪制頁面,每次都要刷新頁面一次。效率也就大打折扣了,而使用document_createDocumentFragment()創建一個文檔碎片,把所有的新結點附加在其上,然后把文檔碎片的內容一次性添加到document中,這也就只需要一次頁面刷新就可。
DocumentFragment類型
在所有節點類型中,只有DocumentFragment在文檔中沒有對應的標記。DOM規定文檔片段(documentfragment)是一種”輕量級“的文檔,可以包含和控制節點,但不會像完整的文檔那樣占用額外資源。DocumentFragment節點具有下列特征:
- nodeType的值為11
- nodeName的值為“#document-fragment”
- nodeValue的值為null
- parentNode的值為null
- 子節點可以是Element、ProcessingInstruction、Comment、Text、CDATASection或EntityReference
雖然不能把文檔片段直接添加到文檔中,但可以將它作為一個“倉庫”來使用,即可以在里面保存將來可能會添加到文檔中的節點。要創建文檔片段,可以使用document.createDocumentFragment()方法,如下所示:
var fragment = document.createDocumentFragment();
文檔片段繼承了Node的所有方法,通常用於執行那些針對文檔的DOM操作。如果將文檔中的節點添加到文檔片段中,就會從文檔樹中再看到該節點。添加到文檔片段中的新節點同樣也不屬於文檔樹。可以通過appendChild()或insertBefore()將文檔片段中內容添加到文檔中。在將文檔片段作為參數傳遞給這兩個方法時,實際上只會將文檔片段的所有子節點添加到相應的位置上;文檔片段本身永遠不會稱為文檔樹的一部分
http://www.w3cmm.com/dom/documentfragment.html
createElement與createDocumentFragment
createElement是創建一個新的節點,createDocumentFragment是創建一個文檔片段
DocumentFragment 接口表示文檔的一部分(或一段)。更確切地說,它表示一個或多個鄰接的 Document 節點和它們的所有子孫節點。
DocumentFragment 節點不屬於文檔樹,繼承的 parentNode 屬性總是 null。
不過它有一種特殊的行為,該行為使得它非常有用
即當請求把一個 DocumentFragment 節點插入文檔樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。這使得 DocumentFragment 成了有用的占位符,暫時存放那些一次插入文檔的節點。它還有利於實現文檔的剪切、復制和粘貼操作,尤其是與 Range 接口一起使用時更是如此
可以用 Document.createDocumentFragment() 方法創建新的空 DocumentFragment 節點。
也可以用 Range.extractContents() 方法 或 Range.cloneContents() 方法 獲取包含現有文檔的片段的 DocumentFragment 節點。
除此之外
createElement創建的元素可以使用innerHTML,createDocumentFragment創建的元素使用innerHTML並不能達到預期修改文檔內容的效果,只是作為一個屬性而已。兩者的節點類型完全不同,並且createDocumentFragment創建的元素在文檔中沒有對應的標記,因此在頁面上只能用js中訪問到
createElement創建的元素可以重復操作,添加之后就算從文檔里面移除依舊歸文檔所有,可以繼續操作,但是createDocumentFragment創建的元素是一次性的,添加之后再就不能操作了
在之前domManip方法中提到的iNoClone多個節點操作需要克隆,就是因為文檔碎片的特性引起的
大體了解了,我們看看jQuery對於節點操作的時候,加強版的文檔碎片buildFragment
buildFragment
我們知道用文檔碎片無非就是先創建
fragment = context.createDocumentFragment(),
然后把所有需要處理的dom節點給appendChild進去
buildFragment對於文檔碎片的創建,可以看到被切分了2個部分
先看第一部分代碼
收集節點元素
我們看一個參數,包含了 字符串,$對象
var $e = $('<span>e</span>'), $x = $('<span>x</span>'); inner.after(' ', $e, ' ', $x)
對應的buildFragment就需要針對傳入elems的分解可以有三部分,引入一個nodes緩存起來
jQuery對象
if ( jQuery.type( elem ) === "object" ) { // Support: QtWebKit // jQuery.merge because core_push.apply(_, arraylike) throws jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
文本類型
nodes.push( context.createTextNode( elem ) )
字符串HTML
將HTML代碼賦值給一個DIV元素的innerHTML屬性,然后取DIV元素的子元素,即可得到轉換后的DOM元素、
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 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: QtWebKit // jQuery.merge because core_push.apply(_, arraylike) throws jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Fixes #12346 // Support: Webkit, IE tmp.textContent = "";
創建了一個臨時的tmp元素(div),這樣調用innerHTML方法,用來儲存創建的節點的內容,fragment本身只是起到一個容器的作用,這點我們要記住了
但是jQuery引入了一個wrapMap,一個反序列化表示
用來干嘛的?
我們知道看jQuery創建元素類型可以是任意的,可以所以可以是是a,scrpit,tr,th,option等等
inner.after('<tr><tr>');
inner.after('<div><div>');
但是在並不是所有元素的的創建都是標准的,在不同瀏覽器下還是有區別,比如表格
比如在table中插入一行一列
var table = document.getElementsByTagName('table')[0]; var tr = document.createElement('tr'); var td = document.createElement('td'); var txt = document.createTextNode('haha'); td.appendChild(txt); tr.appendChild(td); table.appendChild(tr);
面代碼在IE 6上是執行不成功的,大家可以試一下。在IE 8以上的瀏覽器都是好用的。
IE 6上失敗的原因就是IE 6認為tr標簽必須在tbody下面。也就是說,代碼寫成下面這樣,就所有瀏覽器都OK了。
var table = document.getElementsByTagName('table')[0]; var tbody = document.createElement('tbody'); var tr = document.createElement('tr'); var td = document.createElement('td'); var txt = document.createTextNode('haha'); td.appendChild(txt); tr.appendChild(td); tbody.appendChild(tr); table.appendChild(tbody)
所以如果是jQuery插入一個tr標簽,就需要在內部做這樣的處理工作了
inner.after('<tr><tr>');
wrapMap就是用來做適配的
tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
拼寫出來的規則就是
innerHTML: "<table><tbody><tr></tr><tr></tr></tbody></table>"
具體有多少類似的問題我們看看
因為wrapMap容器打破了原來的排列組合所以tr節點位置需要重新定位
就那面這個tr,lastChild變成了table, 所以需要根據wrap[ 0 ]找到嵌套的層數
j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; }
因為fragment現在還不確定是最終的,因為node可能還有其他的節點,所以
fragment.textContent = "";
構建文檔碎片
while ( (elem = nodes[ i++ ]) ) { // #4087 - If origin and destination elements are the same, and this is // that element, do not do anything if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { 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 ); } } } }
處理第一種情況,如果元素和目標元素是相同的
http://bugs.jquery.com/ticket/4087
遍歷每一個元素放入到文檔碎片中
fragment.appendChild( elem )
還有種情況就是寫入的是scrpit標簽了,用的很少先跳過
最終返回fragment