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. 201871010119-帖佼佼《面向对象程序设计(java)》第十周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  2. NodeJS2-1环境&调试----CommonJS

    CommonJS 每个文件是一个模块,有自己的作用域 在模块内部module变量代表模块本身 module.exports属性代表模块对外接口 require规则 /表示绝对路径,./表示型对于当前文 ...

  3. So Easy - 在Linux服务器上部署 .NET Core App

    .NET Core 是微软提供的免费.跨平台和开源的开发框架,可以构建桌面应用程序.移动端应用程序.网络应用程序.物联网应用程序和游戏应用程序等.如果你是 Windows 平台下的 dotnet 开发 ...

  4. 《Java基础知识》Java内部类及其实例化

    在 Java 中,允许在一个类(或方法.语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class). 内部类和外层封装它的类之间存在逻辑上的所属关系 ...

  5. Android.mk语法说明

    版权申明: 本文原创首发于以下网站,您可以自由转载,但必须加入完整的版权声明 博客园:https://www.cnblogs.com/MogooStudio/ csdn博客:https://blog. ...

  6. windows下MySQL解压版安装

    MySQL的安装 一.前期准备 获取MySQL解压版安装包(本文使用的是 [mysql-5.7.28-winx64.zip]版本) 获取方式: 通过官网下载,官方下载地址:“https://dev.m ...

  7. 初级模拟电路:4-3 BJT晶体管的交流建模

    回到目录 1. 四种BJT模型概述 对BJT晶体管建模的基本思路就是,用电路原理中的五大基本元件(电阻.电容.电感.电源.受控源)构建一个电路,使其在一定工作条件下能等效非线性半导体器件的实际工作.一 ...

  8. vue学习笔记(十)路由

    前言 在上一篇博客vue学习笔记(九)vue-cli中的组件通信内容中,我们学习组件通信的相关内容和进行了一些组件通信的小练习,相信大家已经掌握了vue-cli中的组件通信,而本篇博客将会带你更上一层 ...

  9. IT兄弟连 HTML5教程 CSS3属性特效 文字描边

    用CSS3实现的文字描边效果,一个CSS3文字特效实例,字体可以自己随意改,字体颜色也可以自己改.IE9以下浏览器无效果,所以提醒大家测试时候要使用Google Chrome.-webkit-text ...

  10. Java题库——Chapter15 事件驱动编程和动画

    Chapter 15 Event-Driven Programming and Animations Section 15.2 Events and Event Sources1.    A Java ...