jQuery从入门到精通源码分析系列(二十四) DOM操作核心 – buildFragment
沉沙 2018-07-24 来源 : 阅读 1106 评论 0

摘要:本篇jQuery教程探讨了DOM操作核心buildFragment,希望阅读本篇文章以后大家有所收获,帮助大家对jQuery的理解更加深入。

文档碎片是什么

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()将文档片段中内容添加到文档中。在将文档片段作为参数传递给这两个方法时,实际上只会将文档片段的所有子节点添加到相应的位置上;文档片段本身永远不会称为文档树的一部分

//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) throwsjQuery.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 );

            }

        }

    }

}

处理第一种情况,如果元素和目标元素是相同的

//bugs.jquery.com/ticket/4087

遍历每一个元素放入到文档碎片中

fragment.appendChild( elem )

还有种情况就是写入的是scrpit标签了,用的很少先跳过

最终返回fragment


本文由职坐标整理发布,更多相关内容,请关注职坐标WEB前端jQuery频道!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程