之前文章传送门:http://adamed.iteye.com/category/207898
最近工作忙,居然好久没写博客了。好容易写了一点,就先传上来免得自己懈怠了。
下面要讲解的是jQuery1.01构造器中的wrap方法,顾名思义就是将选择器选中的结果集中每一个元素都用某一个HTML元素包装起来。
不多说先上官方API介绍:
.wrap( wrappingElement )
wrappingElement : An HTML snippet, selector expression, jQuery object, or DOM element specifying the structure to wrap around the matched elements.
Description: Wrap an HTML structure around each element in the set of matched elements.
看下实际使用时例子:
HTML代码如下:
<div class="container">
<div class="inner">Hello</div>
<div class="inner">Goodbye</div>
</div>
执行JS代码:
$('.inner').wrap('<div class="new" />');
结果如下:
<div class="container">
<div class="new">
<div class="inner">Hello</div>
</div>
<div class="new">
<div class="inner">Goodbye</div>
</div>
</div>
好了下面上源码:
wrap : function() { // The elements to wrap the target around var a = jQuery.clean( arguments ); // Wrap each of the matched elements individually return this.each( function() { // Clone the structure that we're using to wrap var b = a[0].cloneNode( true ); // Insert it before the element to be wrapped this.parentNode.insertBefore( b , this ); // Find he deepest point in the wrap structure while ( b.firstChild ) b = b.firstChild; // Move the matched element to within the wrap structure b.appendChild( this ); } ); }
代码中首先运行了jQuery.clean这是很多地方都使用的方法:
该方法的作用是将参数转换成DOM元素,这个方法可以说是每次jQuery升级变动最大的一个方法。
基本上每次都会增加很多很多内容,当前我们介绍的还是jQuery最最初级的1.01版所以看起来功能单一了一些。
clean : function( a ) { var r = []; for ( var i = 0; i < a.length; i++ ) { if ( a[i].constructor == String ) { var table = ""; if ( !a[i].indexOf( "<thead" ) || !a[i].indexOf( "<tbody" ) ) { table = "thead"; a[i] = "<table>" + a[i] + "</table>"; } else if ( !a[i].indexOf( "<tr" ) ) { table = "tr"; a[i] = "<table>" + a[i] + "</table>"; } else if ( !a[i].indexOf( "<td" ) || !a[i].indexOf( "<th" ) ) { table = "td"; a[i] = "<table><tbody><tr>" + a[i] + "</tr></tbody></table>"; } var div = document.createElement( "div" ); div.innerHTML = a[i]; if ( table ) { div = div.firstChild; if ( table != "thead" ) div = div.firstChild; if ( table == "td" ) div = div.firstChild; } for ( var j = 0; j < div.childNodes.length; j++ ) r.push( div.childNodes[j] ); } else if ( a[i].jquery || a[i].length && !a[i].nodeType ) for ( var k = 0; k < a[i].length; k++ ) r.push( a[i][k] ); else if ( a[i] !== null ) r.push( a[i].nodeType ? a[i] : document.createTextNode( a[i].toString() ) ); } return r; }
我们看下!a[i].indexOf( "<thead" ) 这一句是什么意思呢?
表面看是判断字符串a[i]是否包含"<thead"还有一个取反的操作。
这里的indexOf如果包含则返回字符串出现的位置,假如a[i]字符串为"<thead>"则a[i].indexOf( "<thead" )返回0,取反则返回true。
这里要注意的是如果不包含(例如"<div>")或者包含但不是起始位置(例如:“<div><thead”)则返回-1或大于0的数字取反之后均返回false。所以上面那句的意思就是类似于 a[i].beginWith("<thead") (beginWith是我自己瞎编的方法不是JS的标准方法哦。)
这里还可以看出1.01还是有一些问题的,比如如果带空格什么的也会造成问题。(例如:" <thead>")
源码中的很多if。。。else if都是类似的这里解释一段:
if ( !a[i].indexOf( "<thead" ) || !a[i].indexOf( "<tbody" ) ) {
table = "thead";
a[i] = "<table>" + a[i] + "</table>";
}
意思就是判断a[i]是否以"<thead"或者"<tbody"开头(" <thead></thead>"这种写法将导致判断失败)如果是则推断:该字符串是thead标签字符串将其放入<table>中最后使用innerHTML生成DOM。
这里看该判断还是有些武断比如如果传入的是"<thead></thead><div />"将导致生成的DOM出现问题。
var div = document.createElement( "div" );
div.innerHTML = a[i];
这2句创建一个新的div将刚才整理出来的字符串使用innerHTML生成DOM。
例如传入:"<thead></thead>"则返回"<table><thead></thead></table>"
生成的DOM就是:
<div>
<table>
<thead></thead>
</table>
</div>
下面的代码:
if ( table ) {
div = div.firstChild;
if ( table != "thead" ) div = div.firstChild;
if ( table == "td" ) div = div.firstChild;
}
我们需要知道的是if("")返回是false 但是if("a")返回是true。知道这些就不难理解上面的代码了
else if ( a[i].jquery || a[i].length && !a[i].nodeType )是判断传入的参数是否为jQuery对象,如果是则迭代放入返回的数组中。
如果传入的参数既不是字符串又不是jQuery的对象则判断该对象是否包含nodeType属性如果包含则认为是DOM否则就调用toString方法生成文本节点(好生猛的处理方式啊。。。。)
a[i].nodeType ? a[i] : document.createTextNode( a[i].toString() )
由于clean方法每个版本变化都非常大,所以其他版本的jQuery的clean方法在降到该版本时再介绍吧。
//***************************这是重要的分界线**********************
我们再回过头来看wrap的代码,在clean处理之后返回数组a之后:
return this.each( function() { // Clone the structure that we're using to wrap var b = a[0].cloneNode( true ); // Insert it before the element to be wrapped this.parentNode.insertBefore( b , this ); // Find he deepest point in the wrap structure while ( b.firstChild ){ b = b.firstChild; } // Move the matched element to within the wrap structure b.appendChild( this ); } );
wrap方法返回的是each方法执行的结果,也就是this。
根据wrap的api,该方法仅传一个字符串类型的参数。处理时我们也是只取a[0]:var b = a[0].cloneNode( true );
如果wrap方法调用时传入的是多个参数那么a的length将大于1,我们忽略除第一个参数以外的其余参数。
变量b指向的是将a[0]克隆(包括子节点)的新节点。
而后将新创建的b节点插入当前节点之前并遍历b节点找到最深层次的首节点而后将当前节点作为最深子节点的子节点。
这里请注意appendChild的这个用法。(W3CSchool里的描述:You can also use the appendChild method to move an element from one element to another.)
//***************************这是重要的分界线**********************
下面将一起讲解4个实现代码类似的方法:
append : function() { return this.domManip( arguments , true , 1 , function( a ) { this.appendChild( a ); } ); }, prepend : function() { return this.domManip( arguments , true , -1 , function( a ) { this.insertBefore( a , this.firstChild ); } ); }, before : function() { return this.domManip( arguments , false , 1 , function( a ) { this.parentNode.insertBefore( a , this ); } ); }, after : function() { return this.domManip( arguments , false , -1 , function( a ) { this.parentNode.insertBefore( a , this.nextSibling ); } ); }
这几个方法作用都很简单通过名字就可以很轻松的判断出来,他们的实现也很类似主要是通过domManip方法来实现的功能。
因为这4个构造器中的方法实现基本类似所以就讲解append和before剩余2个不讲了。
//***************************这是重要的分界线**********************
先上append的API说明:
Description: Insert content, specified by the parameter, to the end of each element in the set of matched elements.
.append( content [, content] )
content : DOM element, HTML string, or jQuery object to insert at the end of each element in the set of matched elements.
content : One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the end of each element in the set of matched elements.
样例说明--HTML代码:
<h2>Greetings</h2>
<div class="container">
<div class="inner">Hello</div>
<div class="inner">Goodbye</div>
</div>
样例说明---JS代码:
$('.inner').append('<p>Test</p>');
样例说明---结果HTML:
<h2>Greetings</h2>
<div class="container">
<div class="inner">
Hello
<p>Test</p>
</div>
<div class="inner">
Goodbye
<p>Test</p>
</div>
</div>
上面的原始HTML若执行:
$('.container').append($('h2'));
则结果HTML为:
<div class="container">
<div class="inner">Hello</div>
<div class="inner">Goodbye</div>
<h2>Greetings</h2>
</div>
注意:append可以接收任意数量的参数。例如:
var $newdiv1 = $('<div id="object1"/>'),
newdiv2 = document.createElement('div'),
existingdiv1 = document.getElementById('foo');
$('body').append($newdiv1, [newdiv2, existingdiv1]);
等价于:$('body').append($newdiv1, newdiv2, existingdiv1);或者:$('body').append([$newdiv1,newdiv2, existingdiv1]);
//***************************这是重要的分界线**********************
由于append实现的主要代码就是一句domManip,故我们先分析一下这个构造器方法的代码:
domManip : function( args , table , dir , fn ) { var clone = this.size() > 1; var a = jQuery.clean( args ); return this.each( function() { var obj = this; if ( table && this.nodeName == "TABLE" && a[0].nodeName != "THEAD" ) { var tbody = this.getElementsByTagName( "tbody" ); if ( !tbody.length ) { obj = document.createElement( "tbody" ); this.appendChild( obj ); } else obj = tbody[0]; } for ( var i = ( dir < 0 ? a.length - 1 : 0 ); i != ( dir < 0 ? dir: a.length ); i += dir ) { fn.apply( obj , [ clone ? a[i].cloneNode( true ) : a[i]] ); } } ); }
此方法的实现和后面的jQuery版本比较起来还是比较简单的。
我们分析一下:
var clone = this.size() > 1;这一句是判断调用domManip的对象是否是一个数组或类数据。
var a = jQuery.clean( args );这一句就更明显了,之前已经讲过clean的用法了,这句的意思就是将args的字符串转换成DOM对象。
遍历this,对于每一个元素执行return的那个方法。
return方法中前面部分判断如果当前this遍历的元素是table则添加一个tbody的子元素而后迭代自身。
主要起作用的方法就是下面的那个for循环。
//***************************这是重要的分界线**********************
现在使用下面的代码来逐句解释源码:
样例说明--HTML代码:
<h2>Greetings</h2>
<div class="container">
<div class="inner">Hello</div>
<div class="inner">Goodbye</div>
</div>
样例说明---JS代码:
$('.inner').append('<p>Test</p>');
解读:
$('.inner')返回jQuery的集合对象里面是[div,div](类似的结构)
调用append:
append : function() {
//这里的this就是那2个div
//arguments就是字符串'<p>Test</p>'
return this.domManip( arguments , true , 1 , function( a ) {
this.appendChild( a );
} );
},
我们在对应一下domManip,由于里面没有table所以就把table部分删掉了:
domManip : function( args , table , dir , fn ) { //args的值为字符串'<p>Test</p>' //table值为true //dir的值为1 //fn为匿名函数 //this就是那2个div,所以clone为true var clone = this.size() > 1; //a的值为DOM对象<p>Test</p> var a = jQuery.clean( args ); //返回的是2个div对象,但是每个对象都执行了匿名方法。 //这里用第一个div举例 return this.each( function() { //obj的值为jQuery对象:<div class="inner">Hello</div> var obj = this; //这里dir=1 ,a.length=1 //相当于:for(var i=0; i!=1; i+=1) for ( var i = ( dir < 0 ? a.length - 1 : 0 ); i != ( dir < 0 ? dir: a.length ); i += dir ) { //clone为true所以相当于: //fn.apply(obj,a[i].cloneNode(true)); fn.apply( obj , [ clone ? a[i].cloneNode( true ) : a[i]] ); /* 也就是相当于调用了: var a=function( a ) { this.appendChild( a ); } $('<div class="inner">Hello</div>').a(); 里面的this为<div class="inner">Hello</div>所以执行完就是 <div class="inner">Hello<p>Test</p></div> */ } } ); }
如果调用的方法是:
prepend : function() { return this.domManip( arguments , true , -1 , function( a ) { this.insertBefore( a , this.firstChild ); } ); }
则最后的那个for循环有一些区别:
for ( var i = ( dir < 0 ? a.length - 1 : 0 ); i != ( dir < 0 ? dir: a.length ); i += dir ) {.....}
//这里dir=-1 ,a.length=1
for ( var i = 0i !=-1; i += -1) {.....}