摘要:歡迎來我的專欄查看系列文章。所以內(nèi)部一個統(tǒng)一的做法,就是采用的方式,先對要進(jìn)行操作的內(nèi)部函數(shù)執(zhí)行操作,然后回調(diào)執(zhí)行任務(wù)。剩下的事情,就交給回調(diào)函數(shù)去處理。所以,的作用遠(yuǎn)比想象的要少。
歡迎來我的專欄查看系列文章。
可能你會覺得這個名字很奇怪這個名字很奇怪,為什么叫做 domManip,即所謂的 dom 微操作。
其實在 jQuery 中有很多重要的 dom 操作,這些操作使用的頻率都非常高,不過這些操作普遍有一個特點,就是需要進(jìn)行微調(diào),比如將字符串轉(zhuǎn)換成 elem 元素,判斷是否為 script 腳本。
所以 jQuery 內(nèi)部一個統(tǒng)一的做法,就是采用 callbacks 的方式,先對要進(jìn)行 dom 操作的內(nèi)部函數(shù)執(zhí)行 domManip 操作,然后回調(diào)執(zhí)行任務(wù)。
jQuery 內(nèi)的一些 DOM 操作函數(shù)jQuery 內(nèi)有幾個方法調(diào)用了 domManip 函數(shù),他們分別如下:
jQuery.fn.extend( { // 在最后一個子元素后添加 append: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); // 原生方法 } } ); }, // 在第一個子元素前添加 prepend: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); // 原生方法 } } ); }, // 在當(dāng)前節(jié)點前添加 before: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); // 原生方法 } } ); }, // 在當(dāng)前節(jié)點后添加 after: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); // 原生方法 } } ); }, replaceWith: function() { var ignored = []; return domManip( this, arguments, function( elem ) { var parent = this.parentNode; if ( jQuery.inArray( this, ignored ) < 0 ) { jQuery.cleanData( getAll( this ) ); if ( parent ) { parent.replaceChild( elem, this ); } } }, ignored ); } } );
仔細(xì)觀察一下,這幾個函數(shù)都有一個特點,就是有關(guān)于 domManip 的參數(shù) domManip(this, arguments, callback),然后在 callback 函數(shù)里面通過原生 js 來實現(xiàn):
// 一個簡單的 jQuery.fn.extend( { append: function(elem){ this[0].appendChild(elem); return this; }, prepend: function(elem){ this[0].insertBefore(elem, this[0].firstChild); return this; }, before: function(elem){ if(this[0].parentNode){ this[0].parentNode.insertBefore(elem, this[0]); } return this; }, after: function(elem){ if(this[0].parentNode){ this[0].parentNode.insertBefore(elem, this[0],nextSibling); } return this; } } );
我之前就跟同學(xué)討論過一個問題,就是如何用原生的方法將字符串轉(zhuǎn)換成 dom 對象,在 jQuery 里面直接jQuery.parseHTML(),原生的話,可以用下面的:
function parseHtml(str){ var div = document.createElement("div"); if(typeof str == "string"){ div.innerHTML = str; } return div.children[0]; }
雖然很是摳腳,但也是一種方法。
buildFragment 方法其實在 jQuery 內(nèi)部,或者說 jQuery.parseHTML 方法之內(nèi),使用的是另外一個方法來建立 str 到 elem 的轉(zhuǎn)換,那就是 buildFragment 方法。這個方法用于建立文檔碎片,你不要糾結(jié)這個方法在 jQuery 中出現(xiàn)幾次,我明確的告訴你,它只在兩個地方出現(xiàn),分別是 domManip 函數(shù)里和 parseHTML 函數(shù)里。
在之前,有必要先了解一下 createDocumentFragment,文中有幾句話說的很好:DocumentFragments are DOM Nodes. They are never part of the main DOM tree. The usual use case is to create the document fragment, append elements to the document fragment and then append the document fragment to the DOM tree. 。它雖然也同樣占內(nèi)存,卻比 createElement 方法好多了。
所以,當(dāng)以后再碰到 create 無需渲染的 dom 的時候,要使用 document.createDocumentFragment 替代 document.createElement。
function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, contains, j, // context 一般為 document fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( jQuery.type( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // 非 HTML 標(biāo)簽 } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // 將 str 轉(zhuǎn)換成 html dom } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // 獲得 標(biāo)簽 類型,處理特殊情況 tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // 一般從 0 開始 j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // 在這里合并到 nodes 里面 jQuery.merge( nodes, tmp.childNodes ); // 返回 div tmp = fragment.firstChild; // 清空 tmp.textContent = ""; } } } // 清空 fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // 跳過已經(jīng)存在的 context if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } contains = jQuery.contains( elem.ownerDocument, elem ); // 添加到 fragment 內(nèi)部,按照順序,并獲得 scripts tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( contains ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; }
最后的返回結(jié)果是 fragment,但它并不是我們想要的 dom,而真正的結(jié)果應(yīng)該是:fragment.childNodes,一個 dom 偽數(shù)組。
domManip 方法其實本文的重點應(yīng)該是 domManip 方法,不急,現(xiàn)在開始來講。
前面已經(jīng)介紹了五個基本的 domManip 用法,下面是幾個擴展,也就是反過來用,也算是間接使用 domManip 吧:
jQuery.each( { appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, ret = [], // 新建一個 jQuery 對象 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 ); // 將 elems 存入 ret push.apply( ret, elems.get() ); } // 返回一個新的 jQuery 對象 return this.pushStack( ret ); }; } );
這又是五個方法,不過是和之前那五個方法剛好先反的邏輯,實用。
來看看 domManip 函數(shù):
function domManip( collection, args, callback, ignored ) { // var concat = [].concat; 用于將偽 args 轉(zhuǎn)換成真是的數(shù)組 args = concat.apply( [], args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], isFunction = jQuery.isFunction( value ); // 處理 WebKit 中出 checked if ( isFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); if ( isFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); } ); } if ( l ) { // 調(diào)用 buildFragment fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); // 第一個 elem first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } // Require either new content or an interest in ignored elements to invoke the callback if ( first || ignored ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length; // Use the original fragment for the last item // instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // 克隆 scripts if ( hasScripts ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } } // 回調(diào),this 指向當(dāng)前回調(diào)的 elem,這點很重要 // 很重要 callback.call( collection[ i ], node, i ); } // 這個 scripts 到底有什么用,不懂 if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src ) { // Optional AJAX dependency, but won"t run scripts if not present if ( jQuery._evalUrl ) { jQuery._evalUrl( node.src ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); } } } } } } return collection; }
在我看來,domManip 主要的幾個功能包括:接受 HTML 字符串,并生成相對于的 dom,callback 回調(diào)函數(shù),處理 dom,而且回調(diào)函數(shù)中的 this 是指向當(dāng)前操作的 dom 的。剩下的事情,就交給回調(diào)函數(shù)去處理。
所以,domManip 的作用遠(yuǎn)比想象的要少。
參考解密jQuery內(nèi)核 DOM操作的核心函數(shù)domManip
解密jQuery內(nèi)核 DOM操作的核心buildFragment
本文在 github 上的源碼地址,歡迎來 star。
歡迎來我的博客交流。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86830.html
摘要:作者按因為教程所示圖片使用的是倉庫圖片,網(wǎng)速過慢的朋友請移步系列教程十五開發(fā)模式與原文地址。而開發(fā)模式就是指定為。在非開發(fā)模式下,需要關(guān)閉此選項,以減小打包體積。在單頁應(yīng)用中,任何響應(yīng)直接被替代為。 作者按:因為教程所示圖片使用的是 github 倉庫圖片,網(wǎng)速過慢的朋友請移步《webpack4 系列教程(十五):開發(fā)模式與 webpack-dev-server》原文地址。更歡迎來我的...
摘要:專題系列共計篇,主要研究日常開發(fā)中一些功能點的實現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實現(xiàn)模式需求我們需要寫一個函數(shù),輸入,返回。 JavaScript 專題之從零實現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現(xiàn)一個 jQuery 的 ext...
摘要:源碼中接受個參數(shù),空參數(shù),這個會直接返回一個空的對象,。,這是一個標(biāo)準(zhǔn)且常用法,表示一個選擇器,這個選擇器通常是一個字符串,或者等,表示選擇范圍,即限定作用,可為,對象。,會把普通的對象或?qū)ο蟀b在對象中。介紹完入口,就開始來看源碼。 歡迎來我的專欄查看系列文章。 init 構(gòu)造器 前面一講總體架構(gòu)已經(jīng)介紹了 jQuery 的基本情況,這一章主要來介紹 jQuery 的入口函數(shù) jQu...
摘要:函數(shù)源碼下面是和的源碼,看了之后肯定有話要說函數(shù)回調(diào)函數(shù)好吧,我承認(rèn),又是同樣的套路,先交給函數(shù)來處理,然后函數(shù),我猜這個時候函數(shù)肯定是采用方式使綁定當(dāng)前。 歡迎來我的專欄查看系列文章。 上一章談到了 dom 的幾個插入操作,雖然插入的方式多種多樣,但只要在懂了原生方法等基礎(chǔ)上,代碼看起來都不是很復(fù)雜。比較有意思的一個函數(shù)就是 buildFragment 方法,用來將 html 字符串...
摘要:的緩存機制來看看中提高的數(shù)據(jù)緩存機制,有兩個函數(shù),分別是和,可以看出來,一個是在對象上,一個是在生成的對象上。而且從源碼來看,的緩存機制自帶清內(nèi)存操作,更是錦上添花呀。參考源碼分析數(shù)據(jù)緩存本文在上的源碼地址,歡迎來。 歡迎來我的專欄查看系列文章。 不打算介紹 deferred,或者放到后面以后去介紹,因為我對于 js 的異步存在著恐懼,看了半天代碼,發(fā)現(xiàn),用挺好用的,一看源碼,全傻眼了...
閱讀 2908·2021-11-17 09:33
閱讀 1630·2021-10-12 10:13
閱讀 2425·2021-09-22 15:48
閱讀 2313·2019-08-29 17:19
閱讀 2587·2019-08-26 11:50
閱讀 1564·2019-08-26 10:37
閱讀 1732·2019-08-23 16:54
閱讀 2916·2019-08-23 14:14