天天看点

jQuery源码分析之wrap,wrapInner,wrapAll,unwrap方法

首先我们回忆一下each实例方法的用法:

<html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
  $("button").click(function(){
    $("li").each(function(i,elem){
     //首先这个each实例函数中,第一个参数是调用对象下标,第二个参数是调用对象DOM元素
     //this指的是调用对象的DOM元素!
    });
  });
});
</script>
</head>
<body>
<button>输出每个列表项的值</button>
<ul>
<li>Coffee</li>
<li>Milk</li>
<li>Soda</li>
</ul>
</body>
</html>
           

我们再次回忆一下contents方法的用法: 点击打开链接

contents: function( elem ) {  
//传入的参数是调用对象的DOM元素,如果是iframe那么获取contentDocument否则获取该元素所有的childNodes也就是NodeList
        return jQuery.nodeName( elem, "iframe" ) ?  
            elem.contentDocument || elem.contentWindow.document :  
            jQuery.merge( [], elem.childNodes );  
    }  
           

参考html元素:

<div class="parent">
	<div class="test">
		111111
	</div>
	<div class="test">
		22222222
	</div>
</div>
<span class="test"></span>
           

wrapAll源码分析:

结论:(如果调用对象所有DOM元素位置不相邻,那么会把他们移动到第一个匹配元素的位置,然后包裹起来)

我们测试代码如下:

$(document).ready(function()
{
  $('.test').wrapAll("<em style='color:red'><span>Hello you</span></em>");
});
           

这时候通过修改后的html结构如下:

<div class="parent">
	<em style="color:red">
             <span>Hello you
              <div class="test">
		111111
	        </div>
               <div class="test">
		22222222
	     </div>
      <span class="test"></span>
    </span>
    </em>
</div>
           

通过这个例子我们应该注意以下几点:

(1)我们的调用对象全部会作为新创建对象的子元素出现的。因为根据源码,第一步把我们新创建的对象放在第一个调用对象的最前面,第二步把所有调用对象通过append放入我们新创建的对象下!

(2)调用对象的DOM元素可能会发生位置移动,如在调用wrapAll之前,span元素是parent的兄弟节点,但是调用之后却成为了子节点了!这是因为,如果已经存在的节点被插入到DOM中其它位置时候,那么该DOM会从原来的位置消失!

jQuery.fn.extend({	
	wrapAll1: function( html ) {
		//如果是函数,那么每一个调用对象调用这个函数,这个函数的上下文是调用对象DOM
		//第一个参数是调用对象的DOM下标,把函数的返回结果作为参数调用wrapAll方法
		if ( jQuery.isFunction( html ) ) {
			return this.each(function(i) {
				jQuery(this).wrapAll( html.call(this, i) );
			});
		}
       //调用对象第一个DOM对象存在
		if ( this[0] ) {
			// The elements to wrap the target around
			//把html作为选择器,获取选择器结果的第一个jQuery对象然后克隆
			//注意这里是克隆,所以参数如果传入的是DOM元素,那么不会从原来的位置上消失!把ownerDocument设置为当前文档!
			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
			//alert(wrap.attr("style"));
           //获取调用对象第一个DOM的父元素,如id="parent"元素
		   //insertBefore接受两个节点,要插入的节点和作为参照的节点
			//alert(this[0].parentNode.className);
			if ( this[0].parentNode ) {
				//wrap是jQuery对象,调用insertBefore方法,所以把构建的wrap对象放在wrapAll第一个调用对象之前!
				wrap.insertBefore( this[0] );
			}		
			//alert(this[0].parentNode.innerHTML);插入到了所有的.test元素的最前了!
             //上下文是调用对象DOM元素,第一个参数是下标,第二个参数是调用对象DOM元素
			//alert(wrap.attr("style"));
			//alert(wrap.html());
			//alert(wrap[0].outerHTML);
			//alert(wrap[0].parentNode.outerHTML);
			$("#result1").html(wrap[0].parentNode.outerHTML);
			var result=wrap.map(function() {	
				var elem = this;
                 // alert( elem.firstChild);
                //获取到调用对象wrap的DOM对象的最前面的一个孩子节点
				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
					elem = elem.firstChild;
				}
               //返回这个孩子节点并添加到wrapAll的调用对象上面去!
				return elem;
			});
			//alert(result.attr("style"));
	//在result的内部的末尾添加wrapAll的调用者对象!也就是把wrapAll调用者对象全部添加到result的子元素的后面!
			//alert(result[0].outerHTML);
			result.append(this);
		}
		return this;
	}
})
//$(".test").wrapAll1("<em style='color:red'></em>");
$(".test").wrapAll1("<em style='color:red'><span>Hello you</span></em>");
           

wrapInner源码分析:

wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each(function(i) {
				//如果传入的是函数,那么对每一个调用对象的DOM元素调用这个函数
				//函数中上下文是调用对象的DOM元素,第一个元素是DOM元素下标
				//把这个函数的调用结果字符串作为新的参数调用wrapInner函数!
				jQuery(this).wrapInner( html.call(this, i) );
			});
		}
		return this.each(function() {
			//不DOM对象封装为jQuery对象
			var self = jQuery( this ),
			//获取jQuery对象的内容,如果是iframe获取contentDocument否则获取childNodes
				contents = self.contents();
               //存在子元素那么用子元素调用wrapAll方法,所以在内部就会用html元素包裹所有的wrapInner
		//调用对象。所以wrapAll使用参数包裹所有的调用对象,wrapInner使用参数对象包裹调用对象所有的子元素!如果没有子元素
		 //就直接添加参数作为子元素!
			if ( contents.length ) {
				contents.wrapAll( html );
			} else {
				//不存在内容直接调用append就可以了,也就是直接在后面添加!
				self.append( html );
			}
		});
	}
           

还是上面的HTML代码,我们调用wrapInner方法:

$(document).ready(function()
{
  $('.test').wrapInner("<em style='color:red'><span>Hello you</span></em>");
});
           

我们发现调用该方法后DOM结构变成了如下

<div class="parent">
	<div class="test">
            <em style="color:red">
                 <span>Hello you
		111111
	        </span>
           </em>
        </div>
	<div class="test">
          <em style="color:red">
              <span>Hello you
		22222222
	       </span>
         </em>
      </div>
</div>
           

其实结果也是很好理解的,因为在wrapInner中他分为两步:第一步获取每一个调用对象的contents,第二步调用wrapAll方法用我们传入的参数把contents全部包裹起来。那么结果就是很简单了, 即每一个调用对象的contents全部被我们参数对象包裹起来!

wrap方法源码分析:

wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );
		return this.each(function(i) {
			//如果是函数,用函数调用结果作为参数调用wrapAll方法,否则用参数直接调用
			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
		});
	}
           

通过源码分析我们也知道wrap方法也调用了wrapAll方法,其结果就是参数包裹了每一个调用对象,而不是调用对象的内容。我们也手动调用wrap方法看看结果如何:

$(document).ready(function()
{
  $('.test').wrap("<em style='color:red'><span>Hello you</span></em>");
});
           

修改后的html如下:

<div class="parent">
	<em style="color:red">
             <span>Hello you
                 <div class="test">
		111111
	        </div>
             </span>
         </em>
	<em style="color:red">
             <span>Hello you
                  <div class="test">
		22222222
	           </div>
             </span>
        </em>
</div>
           

很显然,wrap方法和wrapInner方法的区别是: wrapInner方法用参数包裹了每一个调用对象的内容,而wrap方法用参数包含了每一个调用对象!

unwrap方法源码分析:(该方法移除当前匹配元素的父元素,但会保留所有的内部子元素,但是不会移除body元素!)

unwrap: function() {
		//一直到body位置的parents集合进行迭代
		return this.parent().each(function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				//用该parents集合中的DOM元素封装为jQuery对象,调用replaceWith方法
				//不断往上进行替换!,最后调用end方法
				jQuery( this ).replaceWith( this.childNodes );
			}
		}).end();//之所以调用end是因为前面针对parent进行了一次选择,所以这次end是为了返回this,也就是调用对象,而不是返回父元素
                       //如果没有end那么返回最后一次被替换的父元素,有end那么返回调用者对象自身
           

总结:

(1)wrapAll是用参数包裹所有的调用对象,最后达到所有调用对象都成为参数的最后一个子元素,如果调用对象不相邻那么会发生移动!

(2)wrapInner是用参数包含每一个调用对象子元素(调用对象还是这个参数对象外层的一层包裹),调用对象如果不相邻,那么不会发生移动!

(3)wrap使用调用参数包裹每一个调用对象(不是子元素),如果对象不相邻也不会移动,因为他是通过each逐个包裹的!

wrapAll vs wrap:

相同点:都是在外层包裹(wrap在内部调用wrapAll)

不同点:wrap是逐个包裹,wrapAll是一起包裹!

注意:只有wrapInner是包裹子元素!

继续阅读