jQuery源码解析之after()/insertAfter()/before()/prepend()的实现
前言:
跟
当我调用了
$()
.append()后,jQuery内部发生了什么?
一样,after() / insertAfteer() / before() / prepend(),都会经过
domManip()
和
buildFragment()
的洗礼,最后调用原生JS的方法来实现。
所以,本文只讲述 jQuery 中最后对 after() / insertAfter() / before() / prepend() 处理的相关代码。
想了解
domManip()
和
buildFragment()
的,请看
当我调用了
$()
.append()后,jQuery内部发生了什么?
一、示例HTML
<script src="jQuery.js"></script>
<div id="divTwo">
<p>这是divTwo</p>
<div id="divOne">
<p>这是divOne</p>
<script>
let divOne = document.querySelector("#divOne")
let divTwo = document.querySelector("#divTwo")
</script>
二、after() 作用: 在被选元素 (外部)后 插入 HTML 元素
注意: 会移动已有节点到指定位置
简单实现:
let divOne = document.querySelector("#divOne")
let divTwo = document.querySelector("#divTwo")
//在divOne的第一个child之前插入divTwo
divOne.parentNode.insertBefore(divTwo, divOne.nextSibling)
//上面的等价于
$("#divOne").after($("#divTwo"))
源码:
//在被选元素之后插入指定的内容(不是内部)
//会移动已有节点到指定位置
//http://www.runoob.com/jquery/html-after.html
//源码6225行
after: function() {
return domManip( this, arguments, function( elem ) {
if ( this.parentNode ) {
//a.insertBefore(elem, a.firstElementChild )
//在a的第一个child之前插入elem
//由父节点调用insertBefore,在目标节点的后一节点 的前面插入新节点
this.parentNode.insertBefore( elem, this.nextSibling );
可以看到,在经历了 domManip
的洗礼后,返回符合规范的 elem
即待插入元素,
然后 this
表示 selector ,
在 this
的父节点存在的情况下调用 this.parentNode.insertBefore( elem, this.nextSibling )
完成 after() 操作。三、insertAfter()
作用:在被选元素(外部)后 插入 HTML 元素
$("#divTwo").insertAfter($("#divOne"))
//等价于
$("#divOne").after($("#divTwo"))
注意:和 after() 作用一样,只是调用的元素顺序相反
源码很有意思:
//源码6340行
jQuery.each( {
// 在被选元素(内部)的结尾插入 HTML 元素
appendTo: "append",
// 在被选元素(内部)的开头插入 HTML 元素
prependTo: "prepend",
// 在被选元素(外部)前插入 HTML 元素
insertBefore: "before",
// 在被选元素(外部)后插入 HTML 元素
insertAfter: "after",
// 把被选元素(整个)替换为新的 HTML 元素
replaceAll: "replaceWith"
//key,value
function( name, original ) {
//insertAfter
jQuery.fn[ name ] = function( selector ) {
var elems,
ret = [],
insert = jQuery( selector ),
last = insert.length - 1,
i = 0;
//根据selector的个数来循环
for ( ; i <= last; i++ ) {
//如果有多个选择器,就将待插入的节点深复制
elems = i === last ? this : this.clone( true );
//$().after(elem)
jQuery( insert[ i ] )[ original ]( elems );
// Support: Android <=4.0 only, PhantomJS 1 only
// .get() because push.apply(_, arraylike) throws on ancient WebKit
//push:array.push()
//ret.push(elems.get()) 作用就是将待插入的DOM节点依次放进ret数组中
//$().get():获取selector的DOM元素
push.apply( ret, elems.get() );
//$().pushStack将一个DOM集合压入jQuery栈,并返回DOM集合的jQuery对象,用于链式调用
return this.pushStack( ret );
解析:insertAfter() 是如何偷懒,调用 after()的?
(1)调用 $.each( { key: xxx, value: yyy } , function( key, value ){ } )
依次定义 appendTo()
/ prependTo()
/ insertBefore()
/ insertAfter()
/ replaceAll()
(2)将 selector 和 待插入元素 调换位置
let insert = jQuery( selector )
//$(xxx).after(yyy)
jQuery( insert[ i ] )[ original ]( elems )
(3)根据 selector 的个数作循环,深复制待插入的节点,并依次调用 after()
方法
for ( ; i <= last; i++ ) {
//如果有多个选择器,就将待插入的节点深复制
elems = i === last ? this : this.clone( true );
//$().after(elem)
jQuery( insert[ i ] )[ original ]( elems );
push.apply( ret, elems.get() );
(4)最后,调用 $().pushStack 依次返回处理后 新的 jQuery对象,用于链式调用
return this.pushStack( ret );
注意:appendTo()
/ prependTo()
/ insertBefore()
/ replaceAll()
实现方法类似,就不一一解释了。
四、prepend()
作用:在被选元素(内部)的开头插入 HTML 元素
源码:
//在被选元素内部的开头插入指定内容
prepend: function() {
return domManip( this, arguments, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.insertBefore( elem, target.firstChild );
解析:
//=========prepend()===========
$("#divTwo").prepend($("#divOne"))
//上面的等价于
divTwo.insertBefore( divOne, divTwo.firstChild )
prepend() 其实是调用了 原生 insertBefore() 方法,也就是在 divTwo 内部的第一个子节点前插入 divOne
五、before()
作用:在被选元素(外部)之前插入 HTML 元素
源码:
//在被选元素之前插入指定的内容
//源码6218行
before: function() {
return domManip( this, arguments, function( elem ) {
if ( this.parentNode ) {
this.parentNode.insertBefore( elem, this );