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 =https://www.cnblogs.com/greatdesert/p/=="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的一個封裝而已,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/31413.html
標籤:jQuery
上一篇:js 事件
