jQuery的DOM操作模块封装了DOM模型的insertBefore()、appendChild()、removeChild()、cloneNode()、replaceChild()等原生方法。分为5个子模块来实现:插入元素、删除元素、复制元素、替换元素和包裹元素,本节讲解第一个子模块:插入元素

插入元素模块可用于新增DOM节点,修改文本节点等,API如下:

  • append(content)       ;在被选元素子节点的末尾插入指定内容,内部调用appendChild(elem)方法    ;content可以是HTML代码、函数(返回html代码)、DOM元素 或 jQuery对象,下同
  • prepend(content)        ;在被选元素子节点的头部插入指定内容
  • before(content)           ;在匹配元素集合的每个元素之前插入content。
  • after(content)               ;在匹配元素集合的每个元素之后插入内容
  • appendTo(target)        ;将匹配元素中的每个元素插入目标元素末尾        ;内部调用append()方法    ;这四个方法执行后都会调用pushStack()方法,匹配的是全部插入的DOM对象引用。
  • prependTo(target)       ;将匹配元素中的每个元素插入目标元素开头        ;内部调用prepeng()方法
  • insertBefore(target)     ;将匹配元素中的每个元素插入目标元素之前        ;内部调用before()方法
  • insertAfter(target)        ;将匹配元素中的每个元素插入目标元素之后        ;内部调用after()方法

上面的前四个后面四个是一一对应的,例如:

writer by:大沙漠 QQ:22969969

$('#d').append('<p>1</p>')
$('<p>1</p>').appendTo($('#d')); //这两条代码的作用是相同的

其它几个API也是同样的作用:prepend和prependTo、before和insertBefore、after和insertAfter都是一样的。

举个栗子:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<div id="d">
<p id="p">Hello,World!</p>
</div>
<h1>insertBefore测试</h1>
<h2>insertAfter测试</h2>
<button id="b1">append测试</button>
<button id="b2">prepend测试</button>
<button id="b3">before测试</button>
<button id="b4">insertAfter测试</button>
<script>
$('#b1').click(()=>{
$('#d').append('<p>append测试</p>') //在div元素的子节点末尾添加一个<p>1</p>节点,这里的参数是一个html代码
})
$('#b2').click(()=>{
$('#d').prepend('<p>prepend测试</p>') //在div元素的子节点头部添加一个<p>2</p>节点,这里的参数是一个html代码
})
$('#b3').click(()=>{
$('h1').insertBefore('#d') //在div元素元素之前添加$('h1')元素,这里的参数是一个jQuery对象
})
$('#b4').click(()=>{
$('h2').insertAfter($('#d')) //在div元素元素之后添加$('h2')元素,这里的参数也是一个jQuery对象
})
</script>
</body>
</html>

渲染如下:

对应的DOM树如下:

我们定义了四个按钮,分别在div的子节点之前、子节点默认,div之前位置和div之后位置插入一个DOM元素,每个按钮点击一次,执行后页面如下:

对应的DOM树如下:

源码分析


append、prepend、before和after的实现如下:

jQuery.fn.extend({
append: function() { //在被选元素子节点的末尾插入指定内容,内部调用appendChild(elem)方法
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 ) {
this.appendChild( elem );
}
});
}, prepend: function() { //在被选元素子节点的头部插入指定内容,内部调用insertBefore( elem, this.firstChild )方法
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 ) {
this.insertBefore( elem, this.firstChild );
}
});
}, before: function() { //在匹配元素集合的每个元素之前插入内容
if ( this[0] && this[0].parentNode ) { //如果该元素有父元素
return this.domManip(arguments, false, function( elem ) {
this.parentNode.insertBefore( elem, this );
});
} else if ( arguments.length ) {
var set = jQuery.clean( arguments );
set.push.apply( set, this.toArray() );
return this.pushStack( set, "before", arguments );
}
},
after: function() { //在匹配元素集合的每个元素之后插入内容
if ( this[0] && this[0].parentNode ) { //如果该元素有父元素
return this.domManip(arguments, false, function( elem ) {
this.parentNode.insertBefore( elem, this.nextSibling );
});
} else if ( arguments.length ) {
var set = this.pushStack( this, "after", arguments );
set.push.apply( set, jQuery.clean(arguments) );
return set;
}
},
/*略*/
})

可以看到内部都调用了domManip这个函数,执行时第一个传入arguments,也就是我们调用append、prepend等传入的参数,domManip会把参数转化为一个DOM对象,然后调用参数3,参数3负责执行最后的DOM操作,domManip实现如下:

jQuery.fn.extend({
domManip: function( args, table, callback ) { //jQuery插入元素实现的核心,负责转换html代码为DOM元素,然后调用传入的回调函数插入DOM元素
var results, first, fragment, parent,
value = args[0], //例如$().append()的第一个参数,比如:,<p>123</p>
scripts = []; //存储args[0]里的script脚本 // We can't cloneNode fragments that contain checked, in WebKit
if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
return this.each(function() {
jQuery(this).domManip( args, table, callback, true );
});
} if ( jQuery.isFunction(value) ) { //如果args[0]是函数,则遍历匹配元素集合,执行args[0]函数,并把返回值作为参数,再次遍历执行domManip()函数
return this.each(function(i) {
var self = jQuery(this);
args[0] = value.call(this, i, table ? self.html() : undefined); //在每个元素上执行该函数,返回一个DOM元素
self.domManip( args, table, callback ); //迭代调用.domMapnip()方法
});
} if ( this[0] ) { //如果至少有一个匹配元素
parent = value && value.parentNode; // If we're in a fragment, just use that instead of building a new one
if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
results = { fragment: parent }; //在1.7.1中jQuery.support.parentNode方法未定义,所以这段代码不会执行,在1.8中已经移除这段代码。 } else {
results = jQuery.buildFragment( args, this, scripts ); //调用buildFragment()把html代码转换为DOM元素。返回一个对象。对象里的fragment属性包含了转换后的DOM元素,cacheable表示缓存状态,指示了该HTML元素是否可以缓存
} fragment = results.fragment; //fragment是转换后的DOM元素 if ( fragment.childNodes.length === 1 ) { //如果文档片段只有一个子元素
first = fragment = fragment.firstChild; //重置变量fragment为子元素,因为插入单个子元素比插入含有单个子元素的文档片段会稍快些。
} else {
first = fragment.firstChild; //否则把first作为第一个子节点。
} if ( first ) { //如果子节点存在
table = table && jQuery.nodeName( first, "tr" ); //如果table参数为true,且first是tr节点,那么设置table为true,否则为false。 for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { //遍历当前匹配的元素
callback.call( //执行callback函数,上下文是每个匹配元素,即callback函数内的this指针。参数是转换后的DOM元素,即传给callback的参数,也就是append、prepend等试下内部执行doMainip()传递的参数3这个函数
table ?
root(this[i], first) : //修正目标对象,如果待插入元素是tr元素,则调用函数root()检查当前元素(容器元素)是否是table元素,如果是则返回tr元素的父元素tbody作为目标元素。
this[i], //否则把匹配元素作为call的第一个参数,即this指针。
// Make sure that we do not leak memory by inadvertently discarding
// the original fragment (which might have attached data) instead of
// using it; in addition, use the original fragment object for the last
// item instead of first because it can end up being emptied incorrectly
// in certain situations (Bug #8070).
// Fragments from the fragment cache must always be cloned and never used
// in place.
results.cacheable || ( l > 1 && i < lastIndex ) ? //修正待插入元素,如果返回的文档片段是可缓存的或者不能缓存但是当前匹配元素大于1个且当前操作不是操作最后一个匹配元素时,总是插入该文档片段的副本。
jQuery.clone( fragment, true, true ) : //则总是插入该文档片段的副本,调用jQuery.clone深度复制事件和数据
fragment //否则插入文档本身
);
}
} if ( scripts.length ) { //执行转换后的DOM目标元素的script元素
jQuery.each( scripts, evalScript ); //jQuery.buildFragment()和jQuery.clean()利用浏览器的innerHTML机制把HTML代码转换为DOM元素,但是转换后的script元素并不会自动加载和执行,需要手动处理。
}
} return this; //返回匹配元素集合,以支持链式操作。
}
/*略*/
})

buildFragment是用于将html代码转换为对应的DOM节点的,它首先尝试从缓存中获取对应的DOM对象,如果没有缓存则通过document.createDocumentFragment创建一个文档碎片,然后则再调用$.clean将html代码转换为一个DOM对象(通过设置innerHTML来实现的),代码比较多,不再贴了。

对于appendTo、prependTo、insertBefore和insertAfter来说,它们在会在内部定义一个临时的jQuery对象,然后再调用对应的append、prepend、before和after来实现的,如下:

jQuery.each({
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after",
replaceAll: "replaceWith"
}, function( name, original ) {
jQuery.fn[ name ] = function( selector ) { //给jQuery添加appendTo、prependTo、insertBefore、insertAfter、replaceAll方法 name是要添加的方法名,如appendTo,original标识对应的已有方法名,如append
var ret = [],
insert = jQuery( selector ), //构造一个jQuery对象,指向目标元素集合,这就是内部的临时的jQuery对象
parent = this.length === 1 && this[0].parentNode; //如果操作的html只有一个顶级节点,则把parent设置为父元素引用,否则设为false ;比如:$('<p>222</p>').appendTo('#dd'); if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { //如果待插入元素只有一个,并且是在文档片段中,同时目标元素也只有一个,
insert[ original ]( this[0] ); //则直接将待插入元素插入目标元素,不需要遍历目标元素集合。这样速度稍快一点。
return this; } else {
for ( var i = 0, l = insert.length; i < l; i++ ) { //遍历目标元素集合,循环体内insert[i]是每一个目标元素
var elems = ( i > 0 ? this.clone(true) : this ).get(); //elems是要插入的元素集合,第一次插入的是待插入元素集合,之后插入的则是它的副本。调用对应的已经有方法名插入elems元素
jQuery( insert[i] )[ original ]( elems ); //调用目标元素的original方法,参数是elems
ret = ret.concat( elems );
} return this.pushStack( ret, name, insert.selector ); //用包含了待插入元素集合和它的副本的数组ret构造一个新jQuery对象并返回。
}
};
});

通过代码可以看到appendTo、prependTo、insertBefore和insertAfter只是对append、prepend、before和after的一个封装而已。

jQuery 源码分析(二十) DOM操作模块 插入元素 详解的更多相关文章

  1. jQuery 源码分析(二十一) DOM操作模块 删除元素 详解

    本节说一下DOM操作模块里的删除元素模块,该模块用于删除DOM里的某个节点,也可以理解为将该节点从DOM树中卸载掉,如果该节点有绑定事件,我们可以选择保留或删除这些事件,删除元素的接口有如下三个: e ...

  2. jQuery 源码解析(二十三) DOM操作模块 替换元素 详解

    本节说一下DOM操作模块里的替换元素模块,该模块可将当前匹配的元素替换指定的DOM元素,有两个方法,如下: replaceWith(value)     ;使用提供的新内容来替换匹配元素集合中的每个元 ...

  3. Vue.js 源码分析(二十九) 高级应用 transition-group组件 详解

    对于过度动画如果要同时渲染整个列表时,可以使用transition-group组件. transition-group组件的props和transition组件类似,不同点是transition-gr ...

  4. Vue.js 源码分析(二十八) 高级应用 transition组件 详解

    transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性: n ...

  5. Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解

    普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...

  6. Vue.js 源码分析(二十四) 高级应用 自定义指令详解

    除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上 ...

  7. jQuery 源码解析(二十四) DOM操作模块 包裹元素 详解

    本节说一下DOM操作模块里的包裹元素子模块,该模块可将当前匹配的元素替换指定的DOM元素,有如下方法: wrap(html)               ;在每个匹配元素的外层添加一层DOM元素   ...

  8. jQuery 源码解析(二十二) DOM操作模块 复制元素 详解

    本节说一下DOM操作模块里的复制元素子模块,该模块可以复制一个DOM节点,并且可选择的设置是否复制其数据缓存对象(包含事件信息)和是否深度复制(子孙节点等),API如下: $.clone(elem, ...

  9. jQuery 源码解析(二十七) 样式操作模块 坐标详解

    样式操作模块可用于管理DOM元素的样式.坐标和尺寸,本节讲解一下坐标这一块. 对于坐标来说,jQuery提供了一个offset方法用于获取第一个匹配元素的坐标或者设置所有匹配元素的坐标,还有offse ...

随机推荐

  1. 想要金九银十面试通关,不懂 Java多线程肯定是不行的!

    作者 | 纳达丶无忌 如果对什么是线程.什么是进程仍存有疑惑,请先 Google 之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用 CPU 的资源,因为所有的多线程代码都 ...

  2. Codeforces Round #609 (Div. 2)

    A题 给出n,求大于n的两个合数a和b,并且a-b = n 直接输出n的倍数即可 int n; int main() { cin >> n; cout << 9*n <& ...

  3. Openresty快速安装

    1. 场景描述 软件老王帮同事初始化服务器,需要安装下Openresty,安装过好多次,但是命令还是记不住,这次又安装记录了下,直接按下面的命令next-next就行了,2-3分钟左右就ok了. 2. ...

  4. 转自自己的关于落谷计数器【p1239】的题解

    本蒟蒻写这道题用了两天半里大概五六个小时.(我太弱了) 然后这篇题解将写写我经历的沟沟坎坎,详细的分析一下, 但是由于它很长,因此一定还有多余的地方,比如说我的 预处理,可能比较多余.但是我觉得,信息 ...

  5. Python面向对象-继承和多态特性

    继承 在面向对象的程序设计中,当我们定义一个class时候,可以从现有的class继承,新的class成为子类,被继承的class称为基类,父类或超类. 比如,编写一个名为Animal的class: ...

  6. NPOI 获取单元格的值

    1.日期格式的坑 var cell = row.GetCell(i);//获取某一个单元格 var value = ""; if (cell != null) { if (cell ...

  7. Android: Unable to resolve dependency for ':app@debugUnitTest/compileClasspath':

    我按照ExoPlayer的github指引添加 implementation 'com.google.android.exoplayer:exoplayer:2.X.X' 发现根本run不起来,并报错 ...

  8. java基础 - 锁

    ------------------------ 参考: https://www.cnblogs.com/hustzzl/p/9343797.html https://blog.csdn.net/qq ...

  9. 安装最新版 windows正版软件地址(visio,office)

    链接地址为 https://msdn.itellyou.cn/ 进入后直接搜 然后复制链接使用迅雷下载 很快完成 但是都是原生的 需要破解 提供一个visio的破解软件 亲测有效 链接:https:/ ...

  10. ReactNative: 使用对话框组件AlertIOS组件

    一.简介 在使用一款App的时候,经常会用到对话框进行信息的友好提示,一般简单要求性不高的时候我们可以使用web提供的alert实现即可.但是,对于需要交互性和美观性的对话框,alert就明显无法满足 ...