jquery 实现原理八:DOM操作-manipulate

jquery dom操作有很重要的一点就是对数组的操作,因为jquery的很多操作都是对当前选中的所有元素都起作用的。所以很自然的这个操作会被封装起来。
有两个重要函数会被频繁调用来进行dom操作,分别是domManip和access。

access功能比较简单,可以认为就是一个数组的foreach操作,只是做了一些小的改进:对value回调函数的支持和对链式操作的支持。

domManip是比较复杂的,下面按代码来说明下功能:
domManip: function( args, callback, allowIntersection ) {

          // Flatten any nested arrays
          args = concat.apply( [], args );

          var fragment, first, scripts, hasScripts, node, doc,
               i = 0,
               l = this.length,
               set = this,
               iNoClone = l - 1,
               value = args[ 0 ],
               isFunction = jQuery.isFunction( value );

          // We can't cloneNode fragments that contain checked, in WebKit
          if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
               return this.each(function( index ) {
                    var self = set.eq( index );
                    if ( isFunction ) {
                         args[ 0 ] = value.call( this, index, self.html() );
                    }
                    self.domManip( args, callback, allowIntersection );
               });
          }

          if ( l ) {
               //首先会根据用户传入的参数,创建一个fragment
               fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
               first = fragment.firstChild;

               if ( fragment.childNodes.length === 1 ) {
                    fragment = first;
               }
               
               if ( first ) {
                    //然后下面就是处理script执行问题。
                    //用正则式把script标签提取出来
                    scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
                    hasScripts = scripts.length;

                    // Use the original fragment for the last item instead of the first because it can end up
                    // being emptied incorrectly in certain situations (#8070).
                    for ( ; i < l; i++ ) {
                         node = fragment;
                         
                         //如果不是最后一个元素,则总是clone一个之前创建的fragment
                         if ( i !== iNoClone ) {
                              node = jQuery.clone( node, true, true );

                              // Keep references to cloned scripts for later restoration
                              if ( hasScripts ) {
                                   // Support: QtWebKit
                                   // jQuery.merge because push.apply(_, arraylike) throws
                                   //如果有script标签的话,这里先把type=‘text/javascript’改成’true/text/javascript’,以阻止script的执行
                                   jQuery.merge( scripts, getAll( node, "script" ) );
                              }
                         }

                         callback.call( this[ i ], node, i ); //然后调用回调函数
                    }
                    //因为上面说了,script标签中的代码不会被执行,所以会在下面来通过操作script标签来控制其执行
                    if ( hasScripts ) {
                         doc = scripts[ scripts.length - 1 ].ownerDocument;

                         // Reenable scripts
                         //上面为了阻止js的执行,把type=‘text/javascript’改成’true/text/javascript’,,这里会改回去,但是这样不会触发js的执行。
                         jQuery.map( scripts, restoreScript );

                         // Evaluate executable scripts on first document insertion
                         //因为只改动type属性没有导致script执行,所以这里对每一个script标签,通过js来执行其中的代码,如果是外链js就用 jQuery._evalUrl来执行,如果是内联js就用 jQuery.globalEval来执行。
                         for ( i = 0; i < hasScripts; i++ ) {
                              node = scripts[ i ];
                              if ( rscriptType.test( node.type || "" ) &&
                                   !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {

                                   if ( node.src ) {
                                        // Optional AJAX dependency, but won't run scripts if not present
                                        if ( jQuery._evalUrl ) {
                                             jQuery._evalUrl( node.src );
                                        }
                                   } else {
                                        jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
                                   }
                              }
                         }
                    }
               }
          }

          return this;
     } 

总结下来,domManip主要就做了两件事:
1,根据用户传入的参数,创建了多个fragment,然后通过回调函数参数传入
2,控制script的执行过程,在创建fragment的时候不执行,最后dom操作结束后会统一执行。

弄清楚了domManip 就比较好理解代码了。

可以先看下代码结构:

//首先用extend在原型上拓展了一堆方法,其实每一个的实现都很简单,重点就是要看懂domManip的使用。可以重点挑两个方法的实现看一看,不用每个都看。
jQuery.fn.extend({
     text: function() {},
     append: function() {},
     prepend: function() {},
     before: function() {},
     after: function() {},
     clone: function() {},
     html: function() {},  
     replaceWith: function() {}, 
     domManip: function() {}, 
})

//然后就是appendTo和append之类的处理,大同小异,因为实现差不多,所以就放在一起了。处理的重点就是这一行代码 return this.pushStack( ret )导致操作结束后当前选中的元素发生了变化。
jQuery.each({
     appendTo: "append",
     prependTo: "prepend",
     insertBefore: "before",
     insertAfter: "after",
     replaceAll: "replaceWith"
}, function( name, original ) {
     jQuery.fn[ name ] = function( selector ) {
          var elems,
               ret = [],
               insert = jQuery( selector ),
               last = insert.length - 1,
               i = 0;

          for ( ; i <= last; i++ ) {
               elems = i === last ? this : this.clone( true );
               jQuery( insert[ i ] )[ original ]( elems );

               // Support: QtWebKit
               // .get() because push.apply(_, arraylike) throws
               push.apply( ret, elems.get() );
          }

          return this.pushStack( ret );
     };
}); 


//下面又是很重要的三个函数:clone,buildFragment和cloneData,看名字就很好理解她们分别是做什么用的。
jQuery.extend({
     clone: function( elem, dataAndEvents, deepDataAndEvents ) {
          //三个参数分别是:被clone的元素,是否复制事件和数据,是否复制所有子节点的事件和数据
          //基本过程: 首先调用element.cloneNode来复制元素,然后,如果dataAndEvents == true 则调用cloneCopyEvent函数来复制事件和方法,如果deppDataAndEvents == true 则对每一个子节点都会调用cloneCopyEvent函数来,最后返回复制出来的元素。
     },

     buildFragment: function( elems, context, scripts, selection ) {
          //根据情况来调用 context.createTextNode 或者 context.createElement来创建fragment,其中主要还做了对table的补全操作和对script标签的执行控制。
     },

     cleanData: function( elems ) {
          //删除元素上绑定的事件和数据,通过对每一个元素调用 jQuery.removeEvent 和 delete data_priv.cache[ key ];delete data_user.cache来实现。
     }
}); 

//再下面就是几个工具方法,比较简单,不再一一描述
function manipulationTarget( elem, content ) {
     //修复table结构
} 
function disableScript( elem ) {
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值