首先我们回忆一下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是包裹子元素!