domManip是什麼
dom即Dom元素,Manip是Manipulate的縮寫,連在一起就是Dom操作的意思。
jQuery針對DOM操作的插入的方法有大概10種
append、prepend、before、after、replaceWith
appendTo、prependTo、insertBefore、insertAfter、replaceAll
分2組,上下對照,實現同樣的功能。主要的不同是文法——特別是內容和目標的位置
依賴的domManip,buildFragment模組在之前就分析過了
在匹配元素集合中的每個元素後面插入參數所指定的內容,作為其兄弟節點
對於 .after(), 選擇運算式在函數的前面,參數是將要插入的內容。
對於.insertAfter(), 剛好相反,內容在方法前面,它將被放在參數裡元素的後面。
after
after: function() { return this.domManip( arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); } });},
之前提過了所有的方法靠this.domManip合并參數處理,內部通過buildFragment模組構建文檔片段
然後把每一個方法的具體執行通過回調的方式提供出來處理
DOM操作並未提供一個直接可以在當前節點後插入一個兄弟節點的方法,但是提供了一個類似的方法
insertBefore() 方法:可在已有的子節點前插入一個新的子節點。文法 :insertBefore(newchild,refchild)
看看jQuery如何處理的
例如
inner.after('<p>Test</p>');
內部就會把 '<p>Test</p>' 通過buildFragment構建出文檔elem
然後通過 this.parentNode.insertBefore( elem, this.nextSibling );
這裡的this 就是對應著inner ,elem就是‘<p>Test</p>’
看到這裡就很好理解了after的實現了
用原生方法簡單類比
var inner = document.getElementsByClassName('inner')for(var i =0 ; i<inner.length;i++){ var elem = inner[i] var div = document.createElement('div') div.innerHTML = 'aaaa' elem.parentNode.insertBefore(div,elem.nextSibling)}
insertAfter
jQuery代碼的設計者很聰明的,都儘可能的合并相似功能的方法,代碼更加精鍊美觀
jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith"}, function( name, original ) { jQuery.fn[ name ] = function( selector ) { };});
DEMO
$('<p>Test</p>').insertAfter('.inner');
通過$('<p>Test</p>')構建一個文檔,對象通過insertAfter方法插入到所有class等於inner的節點後
表達的意思與after是一樣的,主要的不同是文法——特別是內容和目標的位置
jQuery.fn[ name ] = function( selector ) { var elems, ret = [], insert = jQuery( selector ), last = insert.length - 1, i = 0; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); // Support: QtWebKit // .get() because core_push.apply(_, arraylike) throws core_push.apply( ret, elems.get() ); } return this.pushStack( ret ); };
看具體的實現方法中.insertAfter('.inner');inner其實就被當作selector傳入進來了
selector可能只是字串選取器內部就需要轉化,insert = jQuery( selector ),
$('<p>Test</p>')就是構建出來的文檔片段節點,那麼如果賦給insert有多個的時候就需要完全複製一份副本了,所以就直接賦給
elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems );
依舊是執行after
jQuery( insert[ i ] )[ original ]( elems );
最終還需要返回這個構建的新節點
收集構建的節點
core_push.apply( ret, elems.get() );
構建一個新jQuery對象,以便實現鏈式
this.pushStack( ret );
可見after 與 insertAfter 本質本質其實都是一樣的,只是通過不同的方式調用
before()
根據參數設定,在匹配元素的前面插入內容
before: function() { return this.domManip( arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); } }); },
類似after只是替換了第二個參數,改變插入的位置
append()
在每個匹配元素裡面的末尾處插入參數內容
append: function() { return this.domManip( arguments, function( elem )
{ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 )
{ var target = manipulationTarget( this, elem );
target.appendChild( elem ); } });},
內部增加節點,直接可以調用appendChild方法
prepend()
將參數內容插入到每個匹配元素的前面(元素內部)
prepend: function() { return this.domManip( arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); } });},
類似after只是替換了第二個參數,改變插入的位置
replaceWith()
用提供的內容替換集合中所有匹配的元素並且返回被刪除元素的集合。
.replaceWith()可以從DOM中移除內容,然後在這個地方插入新的內容。請看下面的例子:
<div class="container"> <div class="inner first">Hello</div> <div class="inner second">And</div> <div class="inner third">Goodbye</div></div>
我們可以用指定的HTML替換第二個 inner <div> :
$('div.second').replaceWith('<h2>New heading</h2>');
結果如下:
<div class="container"> <div class="inner first">Hello</div> <h2>New heading</h2> <div class="inner third">Goodbye</div></div>
或者我們可以選擇一個元素把它當做替換的內容:
$('div.third').replaceWith($('.first'));
結果如下:
<div class="container"> <div class="inner second">And</div> <div class="inner first">Hello</div></div>
從這個例子可以看出,用來替換的元素從老地方移到新位置,而不是複製。
.replaceWith()方法,和大部分其他jQuery方法一樣,返回jQuery對象,所以可以和其他方法連結使用,但是需要注意的是:對於該方法而言,該對象指向已經從 DOM 中被移除的對象,而不是指向替換用的對象。
replaceWith: function() { var // Snapshot the DOM in case .domManip sweeps something relevant into its fragment args = jQuery.map( this, function( elem ) { return [ elem.nextSibling, elem.parentNode ]; }), i = 0; // Make the changes, replacing each context element with the new content this.domManip( arguments, function( elem ) { var next = args[ i++ ], parent = args[ i++ ]; if ( parent ) { // Don't use the snapshot next if it has moved (#13810) if ( next && next.parentNode !== parent ) { next = this.nextSibling; } jQuery( this ).remove(); parent.insertBefore( elem, next ); } // Allow new content to include elements from the context set }, true ); // Force removal if there was no new content (e.g., from empty arguments) return i ? this : this.remove();},
刪除目標節點
jQuery( this ).remove();
然後再插入一個新節點
parent.insertBefore( elem, next );
.domManip()是jQuery DOM操作的核心函數
對封裝的節點操作做了參數上的校正支援,與對應處理的調用
append、prepend、before、after、replaceWith
appendTo、prependTo、insertBefore、insertAfter、replaceAll
為什麼需要用這個domManip函數呢?
我們知道節點操作瀏覽器提供的介面無非就是那麼幾個
appendChild()
通過把一個節點增加到當前節點的childNodes[]組,給文檔樹增加節點。
cloneNode()
複製當前節點,或者複製當前節點以及它的所有子孫節點。
hasChildNodes()
如果當前節點擁有子節點,則將返回true。
insertBefore()
給文檔樹插入一個節點,位置在當前節點的指定子節點之前。如果該節點已經存在,則刪除之再插入到它的位置。
removeChild()
從文檔樹中刪除並返回指定的子節點。
replaceChild()
從文檔樹中刪除並返回指定的子節點,用另一個節點替換它。
以上介面都有一個特性,傳入的是一個節點元素
如果我們傳遞不是一個dom節點元素,如果是一個字串,一個函數或者其他呢,所以針對所有介面的操作,jQuery會抽象出一種參數的處理方案
也就是domManip存在的意義了,針對很多類似介面的參數抽象jQuery內部有很多這樣的函數了,如之前屬性操作中的jQuery.access
jQuery支援的參數傳遞
jquery對接點才操作封裝出了一系列的介面,可以接受HTML字串,DOM 元素,元素數組,或者jQuery對象,用來插在集合中每個匹配元素的不同位置
例如
HTML結構
$('.inner').after('<p>Test</p>');
$對象
$('.container').after($('h2'));
回呼函數
一個返回HTML字串,DOM 元素, 或者 jQuery 對象的函數,插在每個匹配元素的後面。接收元素在集合中的索引位置作為參數。在函數中this指向元素集合中的當前元素
$('p').after(function() { return '<div>' + this.className + '</div>';});
domManip源碼
針對節點的操作有幾個重點的細節
保證最終操作的永遠是dom元素,瀏覽器的最終API只認識那麼幾個介面,所以如果傳遞是字串或者其他的,當然需要轉換
針對節點的大量操作,我們肯定是需要引入文檔片段做最佳化的,這個必不可少
domManip的結構
傳遞的參數, 對應的處理回調,節點是否替換
domManip: function( args, callback, allowIntersection ) {
參數初始化
iNoClone = l - 1, 是否為複製節點,根據後面的大意,如果當前的jQuery對象是一個合集對象花
那麼意味著通過文檔片段構件出來的dom,只能是副本複製到每一個合集對象中
value 是第一個元素,後邊只針對args[0]進行檢測,意味著args中的元素必須是統一類型;
WebKit checked屬性
如果是回呼函數,或者跳過WebKit checked屬性
在WebKit中,不能複製包含了已選中多選按鈕的文檔片段,這有什麼問題?之後在測
文檔片段
將html轉化成dom
其實最終是通過jQuery.buildFragment方法構件出文檔片段
文檔片段的好處就不用多說了,多個繪製操作的時候合并必備
插入頁面
這裡就用了到iNoClone了
一看代碼就很明顯 修正了node節點, 為什麼要修正?
因為通過文檔片段構建出來的只一樣個dom,但是jQuery是一個合集對象,所以都需要用到這個片段了,所以你把args append到第一個元素上了,jQuery執行個體的第二個元素他怎麼辦啊?他沒有可以append的了?!所以,上來要判斷一下執行個體的長度是不是大於1,大於1就需要cloneNode。
callback就是對應了所有api需要執行的操作方法了
jQuery2X是進階版本,所以不相容IE6等低級瀏覽器了,自然在IE6中插入tr,如果漏掉tbody的問題也就不需要修複了
domManip其實就只做了2事件
第一個就是判斷3種傳遞參數所映射的對應操作
第二個就是通過調用jQuery.buildFragment產生文檔片段