jQuery-1.9.1源码分析系列(十一)DOM操作

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

DOM操作包括append、PRepend、before、after、replaceWith、appendTo、prependTo、insertBefore、insertAfter、replaceAll。其核心处理函数是domManip。

DOM操作函数中后五种方法使用的依然是前面五种方法,源码

jQuery.each({

appendTo:"append",

prependTo:"prepend",

insertBefore:"before",

insertAfter:"after",

replaceAll:"replaceWith"},function( name, original ) {

jQuery.fn[ name ]=function( selector ) {varelems,

i= 0,

ret=[],

insert=jQuery( selector ),

last= insert.length - 1;for( ; i <= last; i++) {

elems= i === last ?this:this.clone(true);

jQuery( insert[i] )[ original ]( elems );//现代浏览器调用apply会把jQuery对象当如数组,但是老版本ie需要使用.get()core_push.apply( ret, elems.get() );

}returnthis.pushStack( ret );

};

});

View Code

浏览器原生的插入节点的方法有两个:appendChild和inserBefore,jQuery利用这两个方法拓展了如下方法

jQuery.fn.append使用this.appendChild( elem )

jQuery.fn.prepend使用this.insertBefore( elem, this.firstChild )

jQuery.fn.before使用this.parentNode.insertBefore( elem, this );

jQuery.fn.after使用this.parentNode.insertBefore( elem, this.nextSibling );

jQuery.fn.replaceWith 使用this.parentNode.insertBefore( elem, this.nextSibling);

看一个例子的源码(jQuery.fn.append)

append:function() {returnthis.domManip(arguments,true,function( elem ) {if(this.nodeType === 1 ||this.nodeType === 11 ||this.nodeType === 9) {this.appendChild( elem );

}

});

}

根据上面的源码。猜测domManip的作用是遍历当前jQuery对象所匹配的元素,然后每个元素调用传入的回调,并将要插入的节点(如果是字符串那么需要创建文档碎片节点)作为传入的回调的参数;并执行传入的回调。

接下来分析domManip,看猜测是否正确。dom即Dom元素,Manip是Manipulate的缩写,连在一起的字面意思就是就是Dom操作。

a. domManip: function( args, table, callback )解析args 待插入的DOM元素或HTML代码

table 是否需要修正tbody,这个变量是优化的结果

callback 回调函数,执行格式为callback.call( 目标元素即上下文, 待插入文档碎片/单个DOM元素 )

先看流程,再看细节

第一步,变量初始化。其中iNoClone在后面会用到,如果当前的jQuery对象所匹配的元素不止一个(n > 1)的话,意味着构建出来的文档碎片需要被n用到,则需要被克隆(n-1)次,加上碎片文档本身才够n次使用;value 是第一个参数args的第一个元素,后面会对value是函数做特殊处理;

varfirst, node, hasScripts,

scripts, doc, fragment,

i= 0,

l=this.length,

set=this,

iNoClone= l - 1,

value= args[0],

isFunction= jQuery.isFunction( value );

View Code

第二步,处理特殊下要将当前jQuery对象所匹配的元素一一调用domManip。这种特殊情况有两种:第一种,如果传入的节点是函数(即value是函数)则需要当前jQuery对象所匹配的每个元素都将函数计算出的值作为节点代入domManip中处理。第二种,webkit下,我们不能克隆文含有checked的文档碎片;克隆的文档不能重复使用,那么只能是当前jQuery对象所匹配的每个元素都调用一次domManip处理。

//webkit下,我们不能克隆文含有checked的档碎片if( isFunction || !( l <= 1 ||typeofvalue !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {returnthis.each(function( index ) {varself =set.eq( index );//如果args[0]是函数,则执行函数返回结果替换原来的args[0]if( isFunction ) {

args[0] = value.call(this, index, table ?self.html() : undefined );

}

self.domManip( args, table, callback );

});

}

第三步,处理正常情况,使用传入的节点构建文档碎片,并插入文档中。这里面构建的文档碎片就需要重复使用,区别于第二步的处理。这里面需要注意的是如果是script节点需要在加载完成后执行。顺着源码顺序看一下过程

构建文档碎片

fragment = jQuery.buildFragment( args,this[ 0 ].ownerDocument,false,this);

first=fragment.firstChild;if( fragment.childNodes.length === 1) {

fragment=first;

}

分离出其中的script,这其中有一个函数disableScript更改了script标签的type值以确保安全,原来的type值是"text/javascript",改成了"true/text/Javascript"或"false/text/javascript"

scripts = jQuery.map( getAll( fragment, "script"), disableScript );

hasScripts= scripts.length;

文档碎片插入页面

for( ; i < l; i++) {

node=fragment;if( i !==iNoClone ) {

node= jQuery.clone( node,true,true);//Keep references to cloned scripts for later restorationif( hasScripts ) {

jQuery.merge( scripts, getAll( node,"script") );

}

}

callback.call(

table&& jQuery.nodeName(this[i], "table" ) ?findOrAppend(this[i], "tbody") :this[i],

node,

i

);

}

执行script,分两种情况,远程的使用Ajax来处理,本地的直接执行。

if( hasScripts ) {

doc= scripts[ scripts.length - 1].ownerDocument;//Reenable scriptsjQuery.map( scripts, restoreScript );//在第一个文档插入使执行可执行脚本for( i = 0; i < hasScripts; i++) {

node=scripts[ i ];if( rscriptType.test( node.type || "" ) &&

!jQuery._data( node, "globalEval" ) &&jQuery.contains( doc, node ) ) {if( node.src ) {//Hope ajax is available...jQuery.ajax({

url: node.src,

type:"GET",

dataType:"script",

async:false,

global:false,"throws":true});

}else{

jQuery.globalEval( ( node.text|| node.textContent || node.innerHTML || "" ).replace( rcleanScript, "") );

}

}

}

}

b. dom操作拓展jQuery.fn.text

jQuery.fn.text:function( value ) {returnjQuery.access(this,function( value ) {returnvalue === undefined ?jQuery.text(this) :this.empty().append( (this[0] &&this[0].ownerDocument ||document ).createTextNode( value ) );

},null, value, arguments.length );

}

最终执行value === undefined ?jQuery.text( this ):this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );

其中jQuery.text = Sizzle.getText;

jQuery.fn.html

函数使用jQuery.access来处理

jQuery.fn.html:function( value ) {returnjQuery.access(this,function( value ) {...},null, value, arguments.length );

}

如果没有参数表示是取值

if( value ===undefined ) {returnelem.nodeType === 1 ?elem.innerHTML.replace( rinlinejQuery,"") :

undefined;

}

否则看是否能用innerHTML添加内容。点击参考兼容问题

//看看我们是否可以走了一条捷径,只需使用的innerHTML//需要执行的代码script|style|link等不能使用innerHTML//htmlSerialize:确保link节点能使用innerHTML正确序列化,这就需要在IE浏览器的包装元素//leadingWhitespace:IE strips使用.innerHTML需要以空白开头//不是需要额外添加结束标签或外围包装标签的元素if(typeofvalue === "string" && !rnoInnerhtml.test( value ) &&( jQuery.support.htmlSerialize|| !rnoshimcache.test( value ) ) &&( jQuery.support.leadingWhitespace|| !rleadingWhitespace.test( value ) ) &&

!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {

value= value.replace( rxhtmlTag, "<$1></$2>");try{for(; i < l; i++) {//移除元素节点和缓存,阻止内存泄漏elem =this[i] ||{};if( elem.nodeType === 1) {

jQuery.cleanData( getAll( elem,false) );

elem.innerHTML=value;

}

}

elem= 0;//如果使用innerHTML抛出异常,使用备用方法}catch(e) {}

}

如果不能使用innerHTML或使用不成功(抛出异常),则使用备用方法append

//备用方法,使用append添加节点if( elem ) {this.empty().append( value );

}

jQuery.fn.wrapAll(用单个标签将所有匹配元素包裹起来)

处理步骤:

传入参数是函数则将函数结果传入

if( jQuery.isFunction( html ) ) {returnthis.each(function(i) {

jQuery(this).wrapAll( html.call(this, i) );

});

}

创建包裹层

//获得包裹标签 The elements to wrap the target aroundvarwrap = jQuery( html,this[0].ownerDocument ).eq(0).clone(true);if(this[0].parentNode ) {

wrap.insertBefore(this[0] );

}

用包裹裹住当前jQuery对象

wrap.map(function() {varelem =this;while( elem.firstChild && elem.firstChild.nodeType === 1) {

elem=elem.firstChild;

}returnelem;

}).append(this);

注意:当前jQuery对象匹配的元素最好只有一个,如果有多个的话不推荐使用,这种情况慎用,后面举例可以看到。

简单的例子,原DOM为(后面都使用这个例子)

<div id='center' class="center">

<div id='ss' class="center">

<input type='submit' id='left' class="left">

</div>

</div>

<div class="right">我是right</div>

$('#center').wrapAll("<p></p>")后,dom变成了

<p>

<divid="center"class="center"><divid="ss"class="center"><inputtype="submit"id="left"class="left"></div></div>

</p><divclass="right">我是right</div>

慎用:如果当前jQuery所匹配的元素不止一个,例如原DOM执行$('div').wrapAll(“<p></p>”)后结果DOM变成

<p>

<divid="center"class="center"></div>

<divid="ss"class="center"><inputtype="submit"id="left"class="left"></div>

<divclass="right">我是right</div>

</p>

看到结果了吧,本来#center是#ss的父节点,结果变成了#ss的兄弟节点。

jQuery.fn.wrapInner(在每个匹配元素的所有子节点外部包裹指定的HTML结构)

处理步骤:

传入参数是函数则将函数结果传入

if ( jQuery.isFunction( html ) ) {

return this.each(function(i) {

jQuery(this).wrapInner( html.call(this, i) );

});

}

遍历jQuery对象数组,获取每个元素包含的内容(所有子节点)contents,然后使用warpAll包裹住contents

return this.each(function() {

var self = jQuery( this ),

contents = self.contents();

if ( contents.length ) {

contents.wrapAll( html );

} else {

self.append( html );

}

});

还是使用上面的例子中的原DOM,执行$('div').wrapInner('<p></p>')后结果DOM变成

<divid="center"class="center"><p><divid="ss"class="center"><p><inputtype="submit"id="left"class="left"></p></div></p></div><divclass="right"><p>我是right</p></div>

jQuery.fn.wrap(在每个匹配元素外部包裹指定的HTML结构)

对jQuery的每个元素分别使用wrapAll包裹一下

return this.each(function(i) {

jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );

});

执行$('div').wrap('<p></p>')后结果DOM变成

<p><divid="center"class="center"><p><divid="ss"class="center"><inputtype="submit"id="left"class="left"></div></p></div></p><p><divclass="right">我是right</div></p>

jQuery.fn.unwrap(移除每个匹配元素的父元素)

使用replaceWith用匹配元素父节点的所有子节点替换匹配元素的父节点。当然了父节点是body/html/document肯定是移除不了的

return this.parent().each(function() {

if ( !jQuery.nodeName( this, "body" ) ) {

jQuery( this ).replaceWith( this.childNodes );

}

}).end();

执行$('div').wrap()后结果DOM变成

<divid="ss"class="center"><inputtype="submit"id="left"class="left"></div><divclass="right">我是right</div>

jQuery.fn.remove(从文档中移除匹配的元素)

你还可以使用选择器进一步缩小移除的范围,只移除当前匹配元素中符合指定选择器的部分元素。

与detach()相比,remove()函数会同时移除与元素关联绑定的附加数据( data()函数 )和事件处理器等(detach()会保留)。

for( ; (elem =this[i]) !=null; i++) {if( !selector || jQuery.filter( selector, [ elem ] ).length > 0) {//detach传入的参数keepData为true,不删除缓存if( !keepData && elem.nodeType === 1) {//清除缓存jQuery.cleanData( getAll( elem ) );

}if( elem.parentNode ) {if( keepData &&jQuery.contains( elem.ownerDocument, elem ) ) {

setGlobalEval( getAll( elem,"script") );

}

elem.parentNode.removeChild( elem );

}

}

}

可以看到其中有一个重要的函数cleanData,该方法是用来清除缓存:遍历每一个节点元素,对每一个节点元素做一下处理:

1.获取当前元素对应的缓存

id =elem[ internalKey ];

data= id && cache[ id ];

2.如果有绑定事件,则遍历解绑事件

if( data.events ) {for( typeindata.events ) {if( special[ type ] ) {

jQuery.event.remove( elem, type );//这是一个快捷方式,以避免jQuery.event.remove的开销}else{

jQuery.removeEvent( elem, type, data.handle );

}

}

}

3.如果jQuery.event.remove没有移除cache,则手动移除cache。其中IE需要做一些兼容处理,而且最终会将删除历史保存如core_deletedIds中

//当jQuery.event.remove没有移除cache的时候,移除cacheif( cache[ id ] ) {deletecache[ id ];//IE不允许从节点使用delete删除expando特征,//也能对文件节点使用removeAttribute函数;//我们必须处理所有这些情况下,if( deleteExpando ) {deleteelem[ internalKey ];

}elseif(typeofelem.removeAttribute !==core_strundefined ) {

elem.removeAttribute( internalKey );

}else{

elem[ internalKey ]=null;

}

core_deletedIds.push( id );

}

jQuery.fn.detach

detach:function( selector ) {returnthis.remove( selector,true);

},

jQuery.fn.empty(清空每个匹配元素内的所有内容(所有子节点))

函数将会移除每个匹配元素的所有子节点(包括文本节点、注释节点等所有类型的节点),会清空相应的缓存数据。

for( ; (elem =this[i]) !=null; i++) {//防止内存泄漏移除元素节点缓存if( elem.nodeType === 1) {

jQuery.cleanData( getAll( elem,false) );

}//移除所有子节点while( elem.firstChild ) {

elem.removeChild( elem.firstChild );

}//IE<9,select节点需要将option置空if( elem.options && jQuery.nodeName( elem, "select") ) {

elem.options.length= 0;

}

}

jQuery.fn.clone(克隆当前匹配元素集合的一个副本,并以jQuery对象的形式返回)

克隆是一个比较重要的功能,下一章再细细分析

如果觉得本文不错,请点击右下方【推荐】!

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航