jquery dom操作有很重要的一点就是对数组的操作,因为jquery的很多操作都是对当前选中的所有元素都起作用的。所以很自然的这个操作会被封装起来。
有两个重要函数会被频繁调用来进行dom操作,分别是domManip和access。
access功能比较简单,可以认为就是一个数组的foreach操作,只是做了一些小的改进:对value回调函数的支持和对链式操作的支持。
domManip是比较复杂的,下面按代码来说明下功能:
总结下来,domManip主要就做了两件事:
1,根据用户传入的参数,创建了多个fragment,然后通过回调函数参数传入
2,控制script的执行过程,在创建fragment的时候不执行,最后dom操作结束后会统一执行。
弄清楚了domManip 就比较好理解代码了。
可以先看下代码结构:
有两个重要函数会被频繁调用来进行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 ) {
}