核心函数 jQuery.buildFragment()

 

概述

关于DocumentFragment的讨论

函数定义

修正Document对象doc

是否符合缓存条件

创建文档碎片,转换HTML代码

如果符合缓存条件,将文档碎片放入缓存

返回文档碎片和缓存状态

 

概述

 

DOM操作的三个核心函数的调用链依次是:

.domManip( args, table, callback ) > jQuery.buildFragment( args, nodes, scripts ) > jQuery.clean( elems, context, fragment, scripts )

clip_image002

jQuery.buildFragment()接受.domManip()传入的HTML代码,创建一个文档碎片DocumentFragment,然后调用jQuery.clean()在这个文档碎片上将HTML代码转换为DOM元素,jQuery.buildFragment()的实现有2个亮点值得借鉴:

1. 如果要插入多个DOM元素,可以先将这些DOM元素插入一个文档碎片,然后将文档碎片插入文档中,这时插入的不是文档碎片,而是它的子孙节点;相比于挨个插入DOM元素,使用文档碎片可以获得2-3倍的性能提升;

2. 如果将重复的HTML代码转换为DOM元素,可以将转换后的DOM元素缓存起来,下次(实际是第3次)转换同样的HTML代码时,可以直接缓存的DOM元素克隆返回

这些技巧在后边的源码分析中会详细描述,不过在开始之前,看看关于文档碎片的资料和讨论,可以帮助我们接下来更好的学习源码。

 

关于DocumentFragment的讨论

 

1. John Resig 2008年的一篇文章 http://ejohn.org/blog/dom-documentfragments/

demo: http://ejohn.org/apps/fragment/

测试结果:

Browser Normal(ms) Fragment(ms)
Firefox 3.0.1 90 47
Safari 3.1.2 156 44
Opera 9.51 208 95
IE 6 401 140
IE 7 230 61
IE 8b1 120 40

A method that is largely ignored in modern web development can provide some serious (2-3x) performance improvements to your DOM manipulation.

2. http://msdn.microsoft.com/zh-cn/library/system.xml.xmldocument.createdocumentfragment(pt-br,VS.80).aspx

不能将 DocumentFragment 节点插入文档中。但是,可以将 DocumentFragment 节点的子集插入文档中。

3. http://www.w3school.com.cn/xmldom/dom_documentfragment.asp

XML DOM – DocumentFragment 对象

DocumentFragment 对象表示邻接节点和它们的子树。

DocumentFragment 接口表示文档的一部分(或一段)。更确切地说,它表示一个或多个邻接的 Document 节点和它们的所有子孙节点。

DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。

不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作,尤其是与 Range 接口一起使用时更是如此。

可以用 Document.createDocumentFragment() 方法创建新的空 DocumentFragment 节点。

也可以用 Range.extractContents() 方法 或 Range.cloneContents() 方法 获取包含现有文档的片段的 DocumentFragment 节点。

4. http://www.w3.org/TR/2002/WD-DOM-Level-3-Core-20020409/core.html

When a DocumentFragment is inserted into a Document (or indeed any other Node that may take children) the children of the DocumentFragment and not the DocumentFragment itself are inserted into the Node.

This makes the DocumentFragment very useful when the user wishes to create nodes that are siblings; the DocumentFragment acts as the parent of these nodes so that the user can use the standard methods from the Node interface, such as insertBefore and appendChild.

函数定义

 

   1:      jQuery.buildFragment = function( args, nodes, scripts ) {

 

args 待插入的DOM元素或HTML代码

nodes jQuery对象,从其中获取Document对象,doc = nodes[0].ownerDocument || nodes[0]

scripts 脚本数组,依次传递:.domManip() > jQuery.buildFragment() > jQuery.clean()

 

修正Document对象doc

 

   2:          var fragment, cacheable, cacheresults, doc,
   3:          first = args[ 0 ];
   4:
   5:          // nodes may contain either an explicit document object,
   6:          // a jQuery collection or context object.
   7:          // If nodes[0] contains a valid object to assign to doc
   8:          if ( nodes && nodes[0] ) {
   9:              doc = nodes[0].ownerDocument || nodes[0];
   10:          }
  11:
  12:          // Ensure that an attr object doesn't incorrectly stand in as a document object
  13:          // Chrome and Firefox seem to allow this to occur and will throw exception
  14:          // Fixes #8950
  15:          if ( !doc.createDocumentFragment ) {
  16:              doc = document;
  17:          }
  18:

 

修正doc对象,后边要在doc上调用createDocumentFragment

 

是否符合缓存条件

 

   19:          // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
   20:          // Cloning options loses the selected state, so don't cache them
   21:          // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
   22:          // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
   23:          // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
   24:          if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
   25:              first.charAt(0) === "<" && !rnocache.test( first ) &&
   26:              (jQuery.support.checkClone || !rchecked.test( first )) &&
   27:              (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
  28:
  29:              cacheable = true;
  30:
  31:              cacheresults = jQuery.fragments[ first ];
  32:              if ( cacheresults && cacheresults !== 1 ) {
  33:                  fragment = cacheresults;
  34:              }
  35:          }
  36:

 

先翻译源码注释:

只在主document中缓存1/2KB的HTML(length < 512);

复制options会丢失选中状态,不处理;

在IE6中,如果在fragment中添加<object> <embed>,不能正常工作,不处理;

在WebKit中,复制节点时无法复制checked属性,不处理;

最后,IE6/7/8不能正确的复用缓存的文档碎片,如果碎片是通过未知元素创建的。

 

看看if块需要满足的条件(真是苛刻啊):

args长度等于1 + 第一个元素是字符串 + 字符串长度小于512B + 主文档对象

+ 第一个字符是左尖括号 + 支持缓存的标签(除了/<(?:script|object|embed|option|style)/i)

+ 要么支持正确的checked属性赋值 要么没有checked属性

+ 要么可以正确拷贝HTML5元素 要么不是HTML5标签(在nodeNames中定义)

第29行:cacheable表示first是否满足缓存条件可放入缓存;

第31行:从缓存jQuery.fragments中查找缓存结果,jQuery.fragments是全局的文档碎片缓存对象

第32行:缓存命中,并且不是1,而是真正的文档碎片,1是第一次调用jQuery.buildFragment时设置的,接着往下看

 

创建文档碎片,转换HTML代码

 

   37:          if ( !fragment ) {
   38:              fragment = doc.createDocumentFragment();
   39:              jQuery.clean( args, doc, fragment, scripts );
   40:          }
   41:

 

如果!fragment 为true,创建一个文档碎片,在该文档碎片上调用jQuery.clean将HTML字符串args转换为DOM元素

!fragment 为true,可能的情况:

1. 不符合缓存条件

2. 符合缓存条件 第一次调用jQuery.buildFragment,此时缓存中还没有

3. 符合缓存条件 第二次调用jQuery.buildFragment,此时缓存中是1

 

如果符合缓存条件,将文档碎片放入缓存

 

   42:          if ( cacheable ) {
   43:              jQuery.fragments[ first ] = cacheresults ? fragment : 1;
   44:          }
   45:

 

如果符合缓存条件,那么放入缓存,放入的可能是文档碎片fragment或1,取决于缓存中的已有值;如果已有值是undefiend则为1,如果是1则为fragment。

看到这里,jQuery.buildFragment的使用可以总结为(我们先只考虑满足缓存条件的情况):

第一次 置为1

第二次 置为fragment

第三次 开始享受缓存带来的性能提升

 

返回文档碎片和缓存状态

 

   46:          return { fragment: fragment, cacheable: cacheable };
   47:      };

 

返回Map结构的对象, cacheable 是一个重要的属性,满足缓存条件的fragment在使用时只能克隆,不能用原始的fragment

发表评论

邮箱地址不会被公开。 必填项已用*标注