解密jQuery内核 DOM操作的核心buildFragment

简介:

文档碎片是什么

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('&nbsp;', $e, '&nbsp;', $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>"

具体有多少类似的问题我们看看

image

因为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


本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/p/3510768.html,如需转载请自行联系原作者

相关文章
|
29天前
|
JavaScript 前端开发
使用jQuery操作DOM元素
使用jQuery操作DOM元素
16 1
|
29天前
|
JavaScript 前端开发
JavaScript操作DOM元素
JavaScript操作DOM元素
11 1
|
4天前
|
存储 JavaScript 前端开发
JavaScript DOM 操作:解释一下 cookie、sessionStorage 和 localStorage 的区别。
Cookie是服务器发送至客户端的文本信息,会随每个请求发送回服务器,适合控制会话状态但可能暴露隐私。SessionStorage仅在当前会话中存储数据,关闭浏览器后清除,适合临时存储如登录状态。LocalStorage则持久保存数据,即使关闭浏览器也不会清除,适用于存储长期设置。三种方式各有侧重,应按需求选择。
10 0
|
4天前
|
JavaScript 前端开发 安全
JavaScript DOM 操作:解释一下浏览器的同源策略。
**同源策略**是浏览器安全基石,它阻止脚本跨不同协议、域名或端口访问资源,防止恶意行为。例如,HTTP页面无法直接用JS获取HTTPS页面内容。**CORS**允许跨域请求,但需服务器配合设置,通过`document.domain`属性可配置,但仍受限于服务器配置。
10 4
N..
|
25天前
|
JavaScript 前端开发 容器
jQuery中的DOM操作
jQuery中的DOM操作
N..
13 1
|
30天前
|
前端开发 JavaScript Java
第四章使用jQuery操作DOM元素
第四章使用jQuery操作DOM元素
9 0
|
1月前
|
JavaScript 前端开发
如何使用 JavaScript 操作 DOM?
如何使用 JavaScript 操作 DOM?
13 0
|
4月前
|
JavaScript 前端开发
JavaScript DOM 操作:什么是事件委托(Event Delegation)?有什么优势?
JavaScript DOM 操作:什么是事件委托(Event Delegation)?有什么优势?
55 1
|
4月前
|
JavaScript 前端开发 安全
JavaScript DOM 操作:解释一下 `innerHTML` 和 `textContent` 的区别。
JavaScript DOM 操作:解释一下 `innerHTML` 和 `textContent` 的区别。
42 1
|
18天前
|
JavaScript 前端开发
深入了解 JavaScript 中的 DOM 和 BOM
深入了解 JavaScript 中的 DOM 和 BOM
17 4