• jQuery源码分析系列:.domManip() .buildFragment() .clean()


     

    .domManip(args,table,callback):是jQuery DOM操作的核心函数,可以扩展出如下方法:

      append/appendTo:

      prepend/prependTo: 

      before/insertBefore:

      after/insertAfter:

    1.转换HTML为DOM元素,将args转换为DOM元素,并放在一个文档碎片中,调用jQueyr.buidFragment和jQuery.clean实现。

    2.执行回调函数插入DOM元素。进行callback,将DOM元素作为参数传入,callbacks执行实际操作。

     

    扩展内容:

      insertAdjacentElement、insertAdjacentHTML、insertAdjacentText,在指定的位置插入DOM元素 HTML代码 文本。

    insertAdjacentElement(sWhere,oElement/sText):在sWhere处插入oElement/sText内容。

    可选值

    功能

    jQuery中的等价方法

    beforeBegin

    object之前

    .before()

    afterBegin

    前置,作为object的第一个子元素

    .prepend()

    beforeEnd

    追加,作为object的最后一个子元素

    .append()

    afterEnd

    object之后

    .after()

    domManip(args, table, callback)源码:

            执行步骤:
                1.转换HTML代码为DOM元素
                2.执行回调函数插入DOM元素

            代码流程结构:

                局部变量初始化->规避WebKit checked属性->支持参数为函数->转换HTML为DOM->执行回调函数插入DOM->执行脚本元素

            参数说明:
                args 待插入的DOM元素或HTML代码
                table 是否需要修正tbody 这个变量是优化用的
                callback  回调函数 执行格式为callback.call(目标元素即上下文,待插入文档碎片/单个DOM元素)

     1         domManip: function( args, table, callback ) {
     2             var results, first, fragment, parent,
     3                 value = args[0],//第一个元素,后边只针对args[0]进行检测
     4                 /*
     5                 在jQuery.buildFragment中会用到,脚本的执行在.domManip()的最后一行代码;jQuery.buildFragment中调用jQuery.clean时将
     6                 scripts作为参数传入;jQuery.clean如果遇到script标签,则将script放入scripts,条件是:标签名为script 并且 未指定type或type为text/javascript;
     7                 即支持插入script标签并执行;外联脚本通过jQuery.ajax以同步阻塞的方式请求然后执行,内联脚本通过jQuery.globalEval执行。
     8                 */
     9                 scripts = [];
    10 
    11             //规避WebKit checked属性 不能克隆包含了已选中多选按钮的文档碎片
    12             //不能正确拷贝选中状态&&参数个数为3&&value(args[0])是字符串&&已选中的多选按钮 单选按钮
    13             if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
    14                 return this.each(function() {
    15                     jQuery(this).domManip( args, table, callback, true );
    16                 });
    17             }
    18 
    19             //支持参数为函数  如果value为函数,则将value的执行结果作为args[0]的值  只能够处理一个
    20             if ( jQuery.isFunction(value) ) {//value is function
    21                 return this.each(function(i) {
    22                     var self = jQuery(this);
    23                     //执行函数并返回结果  如果table为true  调用innerHTML修正tbody,
    24                     //用value的返回值替换args[0] 最后用修正过的args 迭代调用.domManip()
    25                     args[0] = value.call(this, i, table ? self.html() : undefined);
    26                     //再次调用domManip
    27                     self.domManip( args, table, callback );
    28                 });
    29             }
    30 
    31             //将HTML转换成DOM
    32             if ( this[0] ) {
    33                 parent = value && value.parentNode;
    34 
    35                 //parent.nodeType === 11 检测父元素是文档碎片(DocumentFragment(nodeTYpe ===1)那么就不用重新创建文档碎片了
    36                 if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
    37                     results = { fragment: parent };//文档碎片就是现有的
    38 
    39                 } else {
    40                     //没有父元素或父元素不是文档碎片,则调用 jQuery.buildFragment 创建一个包含args的文档碎片,jQuery.buildFragment
    41                     //用到了缓存,重复的创建会被缓存下来(需满足一些条件讲到jQuery.buildFragment时会详细分析),
    42                     //jQuery.buildFragment返回的结构是: { fragment: fragment, cacheable: cacheable }
    43                     results = jQuery.buildFragment( args, this, scripts );
    44                 }
    45 
    46                 fragment = results.fragment;//args
    47 
    48                 //获取第一个子元素first,first在后边用于判断是否需要修正tr的父元素为tbody。
    49                 //以第一个元素为准;如果只有一个子元素,那么可以省掉文档碎片;这么做可以更快的插入元素,
    50                 if ( fragment.childNodes.length === 1 ) {//childNodes:返回包含被选节点的子节点的 NodeList
    51                     first = fragment = fragment.firstChild;
    52                 } else {
    53                     first = fragment.firstChild;
    54                 }
    55 
    56                 //执行回调函数插入DOM元素
    57                 if ( first ) {//如果成功创建了DOM元素
    58                     //tr的父元素是tbody  table指示是否需要修正tbody
    59                     table = table && jQuery.nodeName( first, "tr" );//节点有tr
    60                     //遍历当前jQuery对象中的匹配元素,缓存this.length
    61                     for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
    62                         callback.call(//执行callback
    63                             //如果是tr 修正目标元素即上下文
    64                             table ?
    65                                 /*
    66                                 function root( elem, cur ) {//cur干了什么? 不解
    67                                     return jQuery.nodeName(elem, "table") ?    //elem中是否有table标签
    68                                         //返回第一个tbody或者创建一个tbody返回
    69                                         (elem.getElementsByTagName("tbody")[0] ||
    70                                         elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
    71                                         elem;
    72                                 }
    73                                 */
    74                                 root(this[i], first) :    //如果是tr 修正目标元素即上下文
    75                                 this[i],
    76                             //克隆文档碎片  单个DOM元素  缓存的或者this中有多个元素;
    77                             results.cacheable || (l > 1 && i < lastIndex) ?
    78                                 //克隆
    79                                 jQuery.clone( fragment, true, true ) :
    80                                 fragment
    81                         );
    82                     }
    83                 }
    84                 //执行脚本元素  如果脚本数组scripts的长度大于0  则执行其中的脚本;  jQuery.clean中 如果script标签则会放入脚本数组scripts中
    85                 //evalScript负责执行script元素,如果是外联脚本(即通过src引入 src =  " ... " ),用jQuery.ajax同步请求src指定的地址并自动执行;
    86                 //如果是内联脚本(即写在script标签内),用jQuery.globalEval执行。
    87                 if ( scripts.length ) {
    88                     jQuery.each( scripts, evalScript );
    89                 }
    90             }
    91             return this;
    92         }

    jQuery.buildFragment(args,nodes,scripts)源码分析:

        执行步骤:

            创建文档碎片
            转换HTML代码为DOM元素
            缓存

        执行流程:

            修正文档对象doc->是否符合缓存条件->创建文档碎片->转换HTML代码jQuery.clean()->文档碎片放入缓存->返回文档碎片和缓存状态

        关于文档碎片:

            DocumentFragment 接口表示文档的一部分(或一段)。更确切地说,它表示一个或多个邻接的 Document 节点和它们的所有子孙节点。
            DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。
            当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。
            这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。

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

        results = jQuery.buildFragment( args, this, scripts );
        接受.domManip()传入的HTML代码,创建文档碎片DocumentFragment,然后调用jQuery.clean()在这个文档碎片上将HTML代码转换成DOM

        1.如果插入多个DOM元素,可以先将这些DOM插入到文档碎片,然后将文档碎片插入文档。这时插入的是文档碎片的子孙节点,可以提高性能。
        2.将重复的HTML代码转换为DOM元素,可以将转换后的DOM元素缓存起来,下次转换时,直接调用缓存。

        参数说明:

            args:待插入的DOM元素或HTML代码。
            nodes:jQuery对象,从其中获取Docuemnt对象,doc = nodes[0].ownerDocuemnt || nodes[0]
            scripts:脚本数组 依次传递 .domManip() -> jQuery.buildFragment() -> jQuery.clean()

     1 jQuery.buildFragment = function(args,nodes,scripts){
     2     var fragment,
     3         cacheable,
     4         cacheresults,
     5         doc,
     6         first = args[0];
     7 
     8     if(nodes && nodes[0]){
     9         //ownerDocument    返回节点所属的根元素
    10         //第一个节点的根节点或者这个节点
    11         doc = nodes[0].ownerDocument || nodes[0];
    12     }
    13     //如果没有createDocumentFragment属性,则doc为顶层文档
    14     if(!doc.createDocumentFragment){
    15         doc = document;
    16     }
    17 
    18     //args长度等于1&&第一个元素时字符串&&字符串长度小于512&&主文档对象 && 第一个字符时左尖括号 && 支持缓存的标签(除了/<(?:script|object|embed|option|style)/i)
    19     //&& 要么支持正确的checked属性赋值&&要么没有checked属性&&要么支持正确的checked属性赋值&&要么没有checked属性&&要么可以正确拷贝HTML5元素&&要么不是HTML5标签s
    20     if(args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && first.charAt(0) === "<"
    21         && !rnocache.test(first) && (jQuery.support.checkClone || !rchecked.test(first)) &&
    22         (jQuery.support.html5Clone || !rnoshimcache.test(first))){
    23             //表示是否first满足缓存条件放入缓存
    24             cacheable = true;
    25             //从jQuery.fragments中查找缓存结果,jQuery.fragments是全局的文档碎片缓存对象
    26             cacheresults = jQuery.fragments[first];
    27             //缓存名中,并不是1  而是真正的文档碎片 , 1表示第一次调用jQuery.buildFragment时设置
    28             if(cacheresults && cacheresults !== 1){
    29                 fragment = cacheresults;
    30             }
    31     }
    32     /*
    33         1.不符合缓存条件
    34         2.符合缓存条件第一次调用jQuery.buildFragment()此时缓存中还没有
    35         3.符合缓存条件 第二次调用jQuery.buildFragment,此时缓存中是1
    36     */
    37     if(!fragment){
    38         fragment = doc.createDocumentFragment();//创建一个文档碎片
    39         jQuery.clean(args,doc,fragment,scripts);//调用jQuery.clean将HTML字符串args转换成DOM
    40     }
    41     /*
    42         如果符合缓存条件,将文档碎片放入缓存,放入的可能是文档碎片fragment或1,取决于缓存中的已有值;
    43         如果已有值为undefined则为1,如果是1则为fragment
    44 
    45         执行过程
    46         第一次     设置为1,因为已有值为undefined
    47         第二次    设置为fragment,已有值为1
    48         第三次      开始取缓存中的值
    49     */
    50     if(cacheable){
    51         jQuery.fragments[first] = cacheresults ? fragment : 1;
    52     }
    53     //返回文档碎片和缓存状态
    54     return {fragment:fragment,cacheable:cacheable};
    55 }

    clean()源码分析:

             修正文档对象context
            声明返回值
            遍历带转换数组
                遇到HTML代码开始转换
                    不是标签 创建TextNode
                    是标签 开始转换
                        修正XHTML风格的标签
                        创建临时div 插入到安全文档碎片
                        包裹HTML代码 设置innerHTML
                        如果包裹 剥去包裹元素
                        移除IE自动插入的空tbody
                        插入IE自动剔除的空白符
                        取到创建的DOM元素
                    修正IE6/7中defaultChecked属性
                    合并转换后的DOM元素
            提取script元素
            返回转换后的DOM元素数组

            elems         待转换的HTML代码数组
            context     文档对象 会调用context的createTExtNode方法和createElement方法
            fragment    文档碎片 在其上插入div 在div上设置innerHTML
            scripts      脚本数组

      1     clean: function( elems, context, fragment, scripts ) {
      2         //修正文档对象context  后面会调用createTextNode方法和createElement方法
      3         var checkScriptType;
      4         context = context || document;
      5         if ( typeof context.createElement === "undefined" ) {
      6             context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
      7         }
      8 
      9         //声明返回值:ret转换后的DOM元素数组 返回值
     10         var ret = [],
     11             j;//j循环变量 后文循环删除空tbody和修正defaultChecked时用到
     12         //遍历待转换的数组elems(如果是字符串,那就一个字符一个字符的创建TextNode)
     13         for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
     14             //将数字转换成字符串
     15             if ( typeof elem === "number" ) {
     16                 elem += "";
     17             }
     18             //检测非法值,
     19             if ( !elem ) {
     20                 continue;
     21             }
     22 
     23             //遇到HTML代码开始转换
     24             if ( typeof elem === "string" ) {
     25                 //如果不是标签,就创建TextNode 文本节点  rhtml = /<|&#?w+;/;<是HTML标签的起始符号,&#?w+则是特殊符号的起始符号
     26                 if ( !rhtml.test( elem ) ) {
     27                     elem = context.createTextNode( elem );
     28                 } else {//如果是标签,即使HTML代码
     29                     //修正XHTML风格的标签  :XHTML标签修正、保留HTML属性、过滤不需要修正的标签、正确修正未知标签、忽略大小写、多行匹配。
     30                     elem = elem.replace(rxhtmlTag, "<$1></$2>");
     31 
     32                     //创建临时DIV 插入到安全文档碎片
     33                     //从HTML代码中提出来的标签
     34                     var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
     35                         //wrap数组,其中放有tag的深度,父标签,父关闭标签  例如option对应 [ 1, "<select multiple='multiple'>", "</select>" ];
     36                         wrap = wrapMap[ tag ] || wrapMap._default,
     37                         depth = wrap[0],//深度包裹了几层  例如option是1、tr是2、td是3,默认0即不包裹
     38                         //创建一个临时div容器 后边在改div上设置innerHTML
     39                         div = context.createElement("div");
     40 
     41                     //加上包裹标签,设置innerHTML,由浏览器生成DOM元素
     42                     //例如:<option>1</option> 自动加上包裹标签,变成:<select multiple='multiple'><option>1</option></select>
     43                     div.innerHTML = wrap[1] + elem + wrap[2];
     44 
     45                     //比如option自动包裹上select,depth为1,div剥一层是select;也就是说while循环最后的结果是包含了elem的父元素,即div成了elem的父元素;
     46                     //比如tr,div变成tbody;td,div变为tr;thead/tfoot,div变为table,以此类推;即修正elem的父元素。
     47                     while ( depth-- ) {
     48                         div = div.lastChild;
     49                     }
     50 
     51                     //移除IE自动插入的空tbody
     52                     if ( !jQuery.support.tbody ) {
     53                         // String was a <table>, *may* have spurious <tbody>
     54                         var hasBody = rtbody.test(elem),
     55                             tbody = tag === "table" && !hasBody ?
     56                                 div.firstChild && div.firstChild.childNodes :
     57 
     58                                 // String was a bare <thead> or <tfoot>
     59                                 wrap[1] === "<table>" && !hasBody ?
     60                                     div.childNodes :
     61                                     [];
     62 
     63                         for ( j = tbody.length - 1; j >= 0 ; --j ) {
     64                             if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
     65                                 tbody[ j ].parentNode.removeChild( tbody[ j ] );
     66                             }
     67                         }
     68                     }
     69 
     70                     //插入IE自动剔除的空白符
     71                     if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
     72                         div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
     73                     }
     74                     //取到创建的DOM元素 取div的子元素集赋值给elem,elem会被合并到分绘制ret中;
     75                     //如果需要包裹div是待转换元素的父元素,如果不需要包裹 变量div就是DIV元素。所以这里可以简洁地取childNodes
     76                     elem = div.childNodes;
     77 
     78                 }
     79             }
     80 
     81             // 修正radios checkboxes的defaultChecked属性 IE6/7的bug
     82             //在函数findInputs(elem)中找到 input 然后elem.defaultChecked = elem.checked
     83             var len;
     84             if ( !jQuery.support.appendChecked ) {
     85                 if ( elem[0] && typeof (len = elem.length) === "number" ) {
     86                     for ( j = 0; j < len; j++ ) {
     87                         findInputs( elem[j] );
     88                     }
     89                 } else {
     90                     findInputs( elem );
     91                 }
     92             }
     93 
     94             //合并转换后的DOM元素
     95             if ( elem.nodeType ) {//如果是DOM元素直接push,elem本身就是DOM元素
     96                 ret.push( elem );
     97             } else {//elem含有多个元素,合并
     98                 ret = jQuery.merge( ret, elem );
     99             }
    100         }
    101 
    102         //提取script元素
    103         if ( fragment ) {
    104             //检测是否是scriptType元素 rscriptType = //(java|ecma)script/i;rscriptType 标签script的type属性是否是javascript或ecmascrip
    105             checkScriptType = function( elem ) {
    106                 return !elem.type || rscriptType.test( elem.type );
    107             };
    108             for ( i = 0; ret[i]; i++ ) {
    109                 //如果ret[i]的标签名为script并且为指定type或type为text/javascript 放入scripts数组
    110                 if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
    111                     scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
    112 
    113                 } else {
    114                     /*
    115                         取出ret[i]下可能有的的script元素,调用jQuery.grep( elems, callback, inv )过滤,返回其中 未指定type或type包含/javascript或/ecmascript
    116                         的script元素数组jsTags,将这个数组插入ret[i]之后,下次循环时检查。
    117 
    118                         i + 1,将找到的jsTags通过splice插入ret[i],for循环下次遍历ret时,从jsTags开始遍历;splice()方法用于插入、删除或替换数组的元素
    119                     */
    120                     if ( ret[i].nodeType === 1 ) {
    121                         var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
    122 
    123                         ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
    124                     }
    125                     fragment.appendChild( ret[i] );
    126                 }
    127             }
    128         }
    129         //返回转换后的DOM数组
    130         return ret;
    131     },
  • 相关阅读:
    实用工具分享
    美国西储大学轴承数据解读
    CSDN去广告插件
    [教程]SPSS for Mac 安装教程
    [教程]Ubuntu 安装 Docker CE
    [教程]Windows操作系统下安装Ubuntu虚拟机
    Chrome视频解析插件
    [软件]MATLAB小波包的分解与重构
    [信号处理]奈奎斯特采样定理
    [软件]LabVIEW编程实例:如何通过TCP协议进行数据通信
  • 原文地址:https://www.cnblogs.com/colorstory/p/2623095.html
Copyright © 2020-2023  润新知