摘要:它的作用是是用來處理等操作方法的參數的,統一將其處理為類型節點,并交由函數處理,即上圖的。作用將返回的插入到的內部末尾。方法創建了一虛擬的節點對象,節點對象包含所有屬性和方法。
前言:這篇我們倒著講
1、有這樣一個頁面:
注意:不要 append(test1 ),規范寫法是 append( )test1
2、像之前的文章一樣,我們自定義 append() 方法
let ajQuery={} jQuery.each({ //例:"Test
" //源碼6011行-6019行 // 在被選元素的結尾插入指定內容 /*append的內部的原理,就是通過創建一個文檔碎片,把新增的節點放到文檔碎片中,通過文檔碎片克隆到到頁面上去,目的是效率更高*/ append: function(nodelist, arguments) { //node是由domManip處理得到的文檔碎片documentFragment,里面包含要插入的DOM節點 let callbackOne=function( node ) { console.log(node,"node149") //this指的就是$("xxx") //1:元素節點,11:DocumentFragment,9:document if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { //table插入tr的額外判斷 //target默認情況是selector,即document.querySelectorAll(".inner") let target = manipulationTarget( this, node ) console.log(target,node.childNodes,"node147") //append的本質即使用原生appendChild方法在被選元素內部的結尾插入指定內容 target.appendChild( node ); } } console.log(nodelist,arguments,"this120") return domManip( nodelist, arguments, callbackOne ); }, }, function(key, value) { ajQuery[key] = function(nodelist, arguments) { console.log(nodelist,"nodelist128") return value(nodelist, arguments); } } )
3、可以看到,append() 內部調用了 domManip 的方法,接下來重點介紹下該方法
(1)什么是 domManip ?
domManip() 是 jQuery DOM 的核心函數。dom 即 Dom 元素,Manip 是Manipulate 的縮寫,連在一起就是 Dom 操作的意思。
(2)它的作用是?
domManip() 是用來處理 $().append(xxx)、$().after(xxx) 等操作 DOM 方法的參數的,統一將其處理為 DOM 類型節點,并交由 callback 函數處理,即上圖的 callbackOne。
注意: 本文暫不考慮參數包含 的情況,如:
ajQuery.append(innerArr,"alert("append執行script")")
4、domManip() 的三個參數:nodelist, arguments, callbackOne
nodelist:即 document.querySelectorAll(".inner")
arguments:即字符串 " callbackOne:回調函數,在 nodelist、arguments 被相應邏輯處理后會返回一個文檔碎片documentFragment,該方法會對 該文檔碎片進行處理 注意:domMainp 函數講解在 第 8 點。 5、callbackOne() 作用: 將 domManip 返回的 documentFragment 插入到 selector 的內部末尾。 也就是說 $().append() 的本質是 DOM節點.appendChild(處理過的documentFragment(里面包含插入的DOM節點)) 源碼: 6、callbackOne() 中的函數:manipulationTarget() 作用: 額外判斷,當選擇器是table,并且插入的元素是tr時,會查找到table下的tbody,并返回tbody 源碼: 7、manipulationTarget() 中的函數:nodeName() 作用: 判斷兩個參數的nodename是否相等 源碼: 8、jQueryDOM 核心函數:domManip() 作用: 將傳入的參數(dom節點元素、字符串、函數)統一轉化為符合要求的DOM節點 源碼: 解析: 我們可以看到在 目標節點的個數 >=1 的情況下(if(l){xxx}), **注意: 關于 documentFragment,請看文章: jQuery之documentFragment 9、domManip() 中的函數 buildFragment() 作用: 創建文檔碎片 源碼: 解析: (1)創建文檔碎片 documentFragment (2)在 待插入的元素存在的情況下,先在 documentFragment 內部插入 標簽 創建div是為了處理innerHTML的缺陷(IE會忽略開頭的無作用域元素),所以讓所有的元素都被div元素給包含起來,包括script,style等無作用域的元素 (3)但是 比如 (4)documentFragment 在成功添加完子元素后,再卸磨殺驢,去掉包裹的節點,如上例的 (5)最后返回 處理好的文檔碎片 fragment 10、rtagName 作用: 匹配div不支持的標簽,如 tr、td等。 源碼:test1 "
//node是由domManip處理得到的文檔碎片documentFragment,里面包含要插入的DOM節點
let callbackOne=function( node ) {
console.log(node,"node149")
//this指的就是$("xxx")
//1:元素節點,11:DocumentFragment,9:document
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
//table插入tr的額外判斷
//target默認情況是selector,即document.querySelectorAll(".inner")
let target = manipulationTarget( this, node )
console.log(target,node.childNodes,"node147")
//append的本質即使用原生appendChild方法在被選元素內部的結尾插入指定內容
target.appendChild( node );
}
}
//源碼5724行-5733行
//額外判斷,當選擇器是table,并且插入的元素是tr時,會查找到table下的tbody,并返回tbody
//this, node
function manipulationTarget( selector, node ) {
console.log(node.childNodes,node.firstChild,"node73")
// 如果是table里面插入行tr
if ( nodeName( selector, "table" ) &&
nodeName( node.nodeType !== 11 ? node : node.firstChild, "tr" ) ) {
return jQuery( selector ).children( "tbody" )[ 0 ] || selector
}
return selector
}
//源碼2843行-2847行
//判斷兩個參數的nodename是否相等
function nodeName( selector, name ) {
return selector.nodeName && selector.nodeName.toLowerCase() === name.toLowerCase();
}
//源碼5597行-5586行
//作用是將傳入的參數(dom節點元素、字符串、函數)統一轉化為符合要求的DOM節點
//例:$(".inner").append("
")
//nodelist即$(".inner")
//args即Test
function domManip( nodelist, args, callback ) {
console.log(nodelist,args,"ignored5798")
//數組深復制成新的數組副本
//源碼是:args = concat.apply( [], args ),這里沒有用arguments,而是傳參就改了
let argsArr = []
argsArr.push(args)
console.log(argsArr,"args31")
//l 長度,比如類名為.inner的li有兩組,2
let fragment,
first,
node,
i = 0,
//l 長度,比如類名為.inner的li有兩組,2
l = nodelist.length,
iNoClone = l - 1
//l=2
console.log(l,"lll45")
if ( l ) {
console.log(argsArr,nodelist[0].ownerDocument,nodelist,"firstChild40")
//argsArr:Test
//nodelist[0].ownerDocument:目標節點所屬的文檔
fragment = buildFragment(argsArr,nodelist[0].ownerDocument,false,nodelist );
first=fragment.firstChild
console.log(fragment.childNodes,"firstChild42")
//即test1
if (first) {
//=====根據nodelist的長度循環操作========
for ( ; i < l; i++ ) {
console.log(node,fragment.childNodes,"childNodes49")
node = fragment;
if ( i !== iNoClone ) {
/*createDocumentFragment創建的元素是一次性的,添加之后就不能再操作了,
所以需要克隆iNoClone的多個節點*/
node = jQuery.clone( node, true, true );
}
console.log(nodelist[i], node.childNodes,"node50")
//call(this,param)
callback.call( nodelist[i], node);
}
//====================
}
}
console.log(nodelist,"nodelist58")
return nodelist
}test1
調用了 buildFragment() 方法,該方法作用是 創建文檔碎片documentFragment,以便高效地向 目標節點 插入元素,然后根據 目標節點個數 循環地調用 callback 方法,即調用 原生 appendChild 方法插入元素。
由于 createDocumentFragment 創建的元素是一次性的,添加之后就成只讀的了,所以需要克隆 createDocumentFragment創建的元素,以便再次操作。** //源碼4857行-4945行
/*創建文檔碎片,原因是一般情況下,我們向DOM中添加新的元素或者節點,DOM會立刻更新。
如果向DOM添加100個節點,那么就得更新100次,非常浪費瀏覽器資源。
解決辦法就是:我們可以創建一個文檔碎片(documentFragment),
documentFragment類似于一個小的DOM,在它上面使用innerHTML并在innerHTML上插入多個節點,速度要快于DOM(2-10倍),
比如:先將新添加的100個節點添加到文檔碎片的innerHTML上,再將文檔碎片添加到DOM上。*/
//args, collection[ 0 ].ownerDocument, false, collection
function buildFragment( arr, context, truefalse, selection ) {
let elem,tmp, nodes = [], i = 0, l = arr.length,wrap,tag,j
// createdocumentfragment()方法創建了一虛擬的節點對象,節點對象包含所有屬性和方法。
//相當于document.createDocumentFragment()
let fragment = context.createDocumentFragment()
//l=1
console.log(l,"l87")
//==============
for ( ; i < l; i++ ) {
//"
"
elem = arr[ i ];
console.log(i,elem,"elem90")
if ( elem || elem === 0 ) {
/*創建div是為了處理innerHTML的缺陷(IE會忽略開頭的無作用域元素),
讓所有的元素都被div元素給包含起來,包括script,style等無作用域的元素*/
tmp=fragment.appendChild( context.createElement( "div" ) )
//就是匹配div不支持的標簽,如 tr、td等
/*不支持innerHTML屬性的元素,通過正則多帶帶取出處理*/
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
/*作用就是利用wrapMap讓不支持innerHTML的元素通過包裝wrap來支持innerHTML*/
//ie對字符串進行trimLeft操作,其余是用戶輸入處理
//很多標簽不能多帶帶作為DIV的子元素
/*td,th,tr,tfoot,tbody等等,需要加頭尾*/
wrap = wrapMap[ tag ] || wrapMap._default // tr: [ 2, "", "
" ]
console.log(wrap,"wrap152")
//將修正好的element添加進innerHTML中
//jQuery.htmlPrefilter:標簽轉換為閉合標簽,如 -->
/*div不支持tr、td所以需要添加頭尾標簽,如
xxxx
*/
tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
// 因為warp被包裝過,需要找到正確的元素父級
j = wrap[ 0 ]; //2
while ( j-- ) {
tmp = tmp.lastChild;
}
//temp:
//tmp.childNodes:tr
//nodes:[]
//jQuery.merge:將兩個數組合并到第一個數組中
jQuery.merge( nodes, tmp.childNodes );
}
}
//================
// Remove wrapper from fragment
fragment.textContent = "";
//需要將i重置為0
i=0
while ( ( elem = nodes[ i++ ] ) ) {
fragment.appendChild( elem )
}
console.log(fragment.childNodes,"fragment105")
return fragment;
}
let fragment = context.createDocumentFragment()
標簽,會被 wrap 轉為 ,再成功添加到 documentFragment 的 innerHTML 中。
test1 let rtagName = ( /<([a-z][^/