Jquery-1.9.1源碼分析系列(十一)之DOM操作
DOM操作包括append、prepend、before、after、replaceWith、appendTo、prependTo、insertBefore、insertAfter、replaceAll。其核心處理函數(shù)是domManip。
DOM操作函數(shù)中后五種方法使用的依然是前面五種方法,源碼
jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, i = 0, ret = [], insert = jQuery( selector ), last = insert.length - 1; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone(true); jQuery( insert[i] )[ original ]( elems ); //現(xiàn)代瀏覽器調(diào)用apply會(huì)把jQuery對(duì)象當(dāng)如數(shù)組,但是老版本ie需要使用.get() core_push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; });
瀏覽器原生的插入節(jié)點(diǎn)的方法有兩個(gè):appendChild和inserBefore,jQuery利用這兩個(gè)方法拓展了如下方法
jQuery.fn.append使用this.appendChild( elem )
jQuery.fn.prepend使用this.insertBefore( elem, this.firstChild )
jQuery.fn.before使用this.parentNode.insertBefore( elem, this );
jQuery.fn.after使用this.parentNode.insertBefore( elem, this.nextSibling );
jQuery.fn.replaceWith 使用this.parentNode.insertBefore( elem, this.nextSibling);
看一個(gè)例子的源碼(jQuery.fn.append)
append: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.appendChild( elem ); } }); }
根據(jù)上面的源碼。猜測(cè)domManip的作用是遍歷當(dāng)前jQuery對(duì)象所匹配的元素,然后每個(gè)元素調(diào)用傳入的回調(diào),并將要插入的節(jié)點(diǎn)(如果是字符串那么需要?jiǎng)?chuàng)建文檔碎片節(jié)點(diǎn))作為傳入的回調(diào)的參數(shù);并執(zhí)行傳入的回調(diào)。
接下來分析domManip,看猜測(cè)是否正確。dom即Dom元素,Manip是Manipulate的縮寫,連在一起的字面意思就是就是Dom操作。
a. domManip: function( args, table, callback )解析
args 待插入的DOM元素或HTML代碼
table 是否需要修正tbody,這個(gè)變量是優(yōu)化的結(jié)果
callback 回調(diào)函數(shù),執(zhí)行格式為callback.call( 目標(biāo)元素即上下文, 待插入文檔碎片/單個(gè)DOM元素 )
先看流程,再看細(xì)節(jié)
第一步,變量初始化。其中iNoClone在后面會(huì)用到,如果當(dāng)前的jQuery對(duì)象所匹配的元素不止一個(gè)(n > 1)的話,意味著構(gòu)建出來的文檔碎片需要被n用到,則需要被克?。╪-1)次,加上碎片文檔本身才夠n次使用;value 是第一個(gè)參數(shù)args的第一個(gè)元素,后面會(huì)對(duì)value是函數(shù)做特殊處理;
var first, node, hasScripts, scripts, doc, fragment, i = 0, l = this.length, set = this, iNoClone = l - 1, value = args[0], isFunction = jQuery.isFunction( value );
第二步,處理特殊下要將當(dāng)前jQuery對(duì)象所匹配的元素一一調(diào)用domManip。這種特殊情況有兩種:第一種,如果傳入的節(jié)點(diǎn)是函數(shù)(即value是函數(shù))則需要當(dāng)前jQuery對(duì)象所匹配的每個(gè)元素都將函數(shù)計(jì)算出的值作為節(jié)點(diǎn)代入domManip中處理。第二種,webkit下,我們不能克隆文含有checked的文檔碎片;克隆的文檔不能重復(fù)使用,那么只能是當(dāng)前jQuery對(duì)象所匹配的每個(gè)元素都調(diào)用一次domManip處理。
//webkit下,我們不能克隆文含有checked的檔碎片 if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { return this.each(function( index ) { var self = set.eq( index ); //如果args[0]是函數(shù),則執(zhí)行函數(shù)返回結(jié)果替換原來的args[0] if ( isFunction ) { args[0] = value.call( this, index, table ? self.html() : undefined ); } self.domManip( args, table, callback ); }); }
第三步,處理正常情況,使用傳入的節(jié)點(diǎn)構(gòu)建文檔碎片,并插入文檔中。這里面構(gòu)建的文檔碎片就需要重復(fù)使用,區(qū)別于第二步的處理。這里面需要注意的是如果是script節(jié)點(diǎn)需要在加載完成后執(zhí)行。順著源碼順序看一下過程
構(gòu)建文檔碎片
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; }
分離出其中的script,這其中有一個(gè)函數(shù)disableScript更改了script標(biāo)簽的type值以確保安全,原來的type值是"text/javascript",改成了"true/text/javascript"或"false/text/javascript"
scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length;
文檔碎片插入頁面
for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( table && jQuery.nodeName( this[i], "table" ) ? findOrAppend( this[i], "tbody" ) : this[i], node, i ); }
執(zhí)行script,分兩種情況,遠(yuǎn)程的使用ajax來處理,本地的直接執(zhí)行。
if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); //在第一個(gè)文檔插入使執(zhí)行可執(zhí)行腳本 for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src ) { // Hope ajax is available... jQuery.ajax({ url: node.src, type: "GET", dataType: "script", async: false, global: false, "throws": true }); } else { jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); } } } }
b. dom操作拓展
jQuery.fn.text
jQuery.fn.text: function( value ) { return jQuery.access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); }, null, value, arguments.length ); }
最終執(zhí)行value === undefined ? jQuery.text( this ) : this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
其中jQuery.text = Sizzle.getText;
jQuery.fn.html
函數(shù)使用jQuery.access來處理
jQuery.fn.html: function( value ) { return jQuery.access( this, function( value ) {...}, null, value, arguments.length ); }
如果沒有參數(shù)表示是取值
if ( value === undefined ) { return elem.nodeType === 1 ? elem.innerHTML.replace( rinlinejQuery, "" ) : undefined; }
否則看是否能用innerHTML添加內(nèi)容。點(diǎn)擊參考兼容問題
//看看我們是否可以走了一條捷徑,只需使用的innerHTML //需要執(zhí)行的代碼script|style|link等不能使用innerHTML //htmlSerialize:確保link節(jié)點(diǎn)能使用innerHTML正確序列化,這就需要在IE瀏覽器的包裝元素 //leadingWhitespace:IE strips使用.innerHTML需要以空白開頭 //不是需要額外添加結(jié)束標(biāo)簽或外圍包裝標(biāo)簽的元素 if ( typeof value === "string" && !rnoInnerhtml.test( value ) && ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { value = value.replace( rxhtmlTag, "<$1></$2>" ); try { for (; i < l; i++ ) { //移除元素節(jié)點(diǎn)和緩存,阻止內(nèi)存泄漏 elem = this[i] || {}; if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; //如果使用innerHTML拋出異常,使用備用方法 } catch(e) {} }
如果不能使用innerHTML或使用不成功(拋出異常),則使用備用方法append
//備用方法,使用append添加節(jié)點(diǎn) if ( elem ) { this.empty().append( value ); } jQuery.fn.wrapAll(用單個(gè)標(biāo)簽將所有匹配元素包裹起來) 處理步驟: 傳入?yún)?shù)是函數(shù)則將函數(shù)結(jié)果傳入 if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapAll( html.call(this, i) ); }); } 創(chuàng)建包裹層 //獲得包裹標(biāo)簽 The elements to wrap the target around var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); if ( this[0].parentNode ) { wrap.insertBefore( this[0] ); }
用包裹裹住當(dāng)前jQuery對(duì)象
wrap.map(function() { var elem = this; while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { elem = elem.firstChild; } return elem; }).append( this );
注意:當(dāng)前jQuery對(duì)象匹配的元素最好只有一個(gè),如果有多個(gè)的話不推薦使用,這種情況慎用,后面舉例可以看到。
簡(jiǎn)單的例子,原DOM為(后面都使用這個(gè)例子)
<div id='center' class="center"> <div id='ss' class="center"> <input type='submit' id='left' class="left"> </div> </div> <div class="right">我是right</div> $('#center').wrapAll("<p></p>")后,dom變成了 <p> <div id="center" class="center"> <div id="ss" class="center"> <input type="submit" id="left" class="left"> </div> </div> </p> <div class="right">我是right</div>
慎用:如果當(dāng)前jQuery所匹配的元素不止一個(gè),例如原DOM執(zhí)行$('div').wrapAll(“<p></p>”)后結(jié)果DOM變成
<p> <div id="center" class="center"></div> <div id="ss" class="center"> <input type="submit" id="left" class="left"> </div> <div class="right">我是right</div> </p>
看到結(jié)果了吧,本來#center是#ss的父節(jié)點(diǎn),結(jié)果變成了#ss的兄弟節(jié)點(diǎn)。
jQuery.fn.wrapInner(在每個(gè)匹配元素的所有子節(jié)點(diǎn)外部包裹指定的HTML結(jié)構(gòu))
處理步驟:
傳入?yún)?shù)是函數(shù)則將函數(shù)結(jié)果傳入
if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapInner( html.call(this, i) ); }); }
遍歷jQuery對(duì)象數(shù)組,獲取每個(gè)元素包含的內(nèi)容(所有子節(jié)點(diǎn))contents,然后使用warpAll包裹住contents
return this.each(function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } });
還是使用上面的例子中的原DOM,執(zhí)行$('div').wrapInner('<p></p>')后結(jié)果DOM變成
<div id="center" class="center"> <p> <div id="ss" class="center"> <p> <input type="submit" id="left" class="left"> </p> </div> </p> </div> <div class="right"> <p> 我是right </p> </div>
jQuery.fn.wrap(在每個(gè)匹配元素外部包裹指定的HTML結(jié)構(gòu))
對(duì)jQuery的每個(gè)元素分別使用wrapAll包裹一下
return this.each(function(i) { jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); });
行$('div').wrap('<p></p>')后結(jié)果DOM變成
<p> <div id="center" class="center"> <p> <div id="ss" class="center"> <input type="submit" id="left" class="left"> </div> </p> </div> </p> <p> <div class="right">我是right</div> </p>
jQuery.fn.unwrap(移除每個(gè)匹配元素的父元素)
使用replaceWith用匹配元素父節(jié)點(diǎn)的所有子節(jié)點(diǎn)替換匹配元素的父節(jié)點(diǎn)。當(dāng)然了父節(jié)點(diǎn)是body/html/document肯定是移除不了的
return this.parent().each(function() { if ( !jQuery.nodeName( this, "body" ) ) { jQuery( this ).replaceWith( this.childNodes ); } }).end(); 執(zhí)行$('div').wrap()后結(jié)果DOM變成 <div id="ss" class="center"> <input type="submit" id="left" class="left"> </div> <div class="right">我是right</div>
jQuery.fn.remove(從文檔中移除匹配的元素)
你還可以使用選擇器進(jìn)一步縮小移除的范圍,只移除當(dāng)前匹配元素中符合指定選擇器的部分元素。
與detach()相比,remove()函數(shù)會(huì)同時(shí)移除與元素關(guān)聯(lián)綁定的附加數(shù)據(jù)( data()函數(shù) )和事件處理器等(detach()會(huì)保留)。
for ( ; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { // detach傳入的參數(shù)keepData為true,不刪除緩存 if ( !keepData && elem.nodeType === 1 ) { //清除緩存 jQuery.cleanData( getAll( elem ) ); } if ( elem.parentNode ) { if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { setGlobalEval( getAll( elem, "script" ) ); } elem.parentNode.removeChild( elem ); } } }
可以看到其中有一個(gè)重要的函數(shù)cleanData,該方法是用來清除緩存:遍歷每一個(gè)節(jié)點(diǎn)元素,對(duì)每一個(gè)節(jié)點(diǎn)元素做一下處理:
1.獲取當(dāng)前元素對(duì)應(yīng)的緩存
id = elem[ internalKey ]; data = id && cache[ id ];
2.如果有綁定事件,則遍歷解綁事件
if ( data.events ) { for ( type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); //這是一個(gè)快捷方式,以避免jQuery.event.remove的開銷 } else { jQuery.removeEvent( elem, type, data.handle ); } } }
3.如果jQuery.event.remove沒有移除cache,則手動(dòng)移除cache。其中IE需要做一些兼容處理,而且最終會(huì)將刪除歷史保存如core_deletedIds中
//當(dāng)jQuery.event.remove沒有移除cache的時(shí)候,移除cache if ( cache[ id ] ) { delete cache[ id ]; //IE不允許從節(jié)點(diǎn)使用delete刪除expando特征, //也能對(duì)文件節(jié)點(diǎn)使用removeAttribute函數(shù); //我們必須處理所有這些情況下, if ( deleteExpando ) { delete elem[ internalKey ]; } else if ( typeof elem.removeAttribute !== core_strundefined ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } core_deletedIds.push( id ); }
jQuery.fn.detach
detach: function( selector ) { return this.remove( selector, true ); },
jQuery.fn.empty(清空每個(gè)匹配元素內(nèi)的所有內(nèi)容(所有子節(jié)點(diǎn)))
函數(shù)將會(huì)移除每個(gè)匹配元素的所有子節(jié)點(diǎn)(包括文本節(jié)點(diǎn)、注釋節(jié)點(diǎn)等所有類型的節(jié)點(diǎn)),會(huì)清空相應(yīng)的緩存數(shù)據(jù)。
for ( ; (elem = this[i]) != null; i++ ) { //防止內(nèi)存泄漏移除元素節(jié)點(diǎn)緩存 if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); } //移除所有子節(jié)點(diǎn) while ( elem.firstChild ) { elem.removeChild( elem.firstChild ); } // IE<9,select節(jié)點(diǎn)需要將option置空 if ( elem.options && jQuery.nodeName( elem, "select" ) ) { elem.options.length = 0; } }
- AJAX 驗(yàn)證框架13個(gè)
- jquery 框架使用教程 AJAX篇
- Jquery AJAX 框架的使用方法
- 基于JQuery框架的AJAX實(shí)例代碼
- javascript之AJAX框架使用說明
- asp.net省市三級(jí)聯(lián)動(dòng)的DropDownList+Ajax的三種框架(aspnet/Jquery/ExtJs)示例
- 簡(jiǎn)單的前端js+ajax 購(gòu)物車框架(入門篇)
- jQuery1.9.1針對(duì)checkbox的調(diào)整方法(prop)
- 零基礎(chǔ)學(xué)習(xí)AJAX之AJAX框架
- jQuery 1.9.1源碼分析系列(十)事件系統(tǒng)之綁定事件
- jQuery-1.9.1源碼分析系列(十)事件系統(tǒng)之事件體系結(jié)構(gòu)
- jQuery-1.9.1源碼分析系列(十)事件系統(tǒng)之事件包裝
- Jquery1.9.1源碼分析系列(六)延時(shí)對(duì)象應(yīng)用之jQuery.ready
- jQuery 1.9.1源碼分析系列(十三)之位置大小操作
- jQuery 1.9.1源碼分析系列(十四)之常用jQuery工具
- jQuery1.9.1源碼分析系列(十六)ajax之a(chǎn)jax框架
相關(guān)文章
基于jquery trigger函數(shù)無法觸發(fā)a標(biāo)簽的兩種解決方法
下面小編就為大家分享一篇基于jquery trigger函數(shù)無法觸發(fā)a標(biāo)簽的兩種解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01Jquery的基本對(duì)象轉(zhuǎn)換和文檔加載用法實(shí)例
這篇文章主要介紹了Jquery的基本對(duì)象轉(zhuǎn)換和文檔加載用法,實(shí)例分析了Jquery的基本對(duì)象轉(zhuǎn)換及文檔加載使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02jQuery幻燈片特效代碼分享 鼠標(biāo)滑過按鈕時(shí)切換(2)
本文實(shí)例講述了jQuery實(shí)現(xiàn)時(shí)尚漂亮的幻燈片特效,基本能滿足你在網(wǎng)頁上使用幻燈片(焦點(diǎn)圖)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-08-08得到j(luò)Query detach()后節(jié)點(diǎn)中的某個(gè)值實(shí)現(xiàn)代碼
需要jQuery -detach 后的dom 結(jié)構(gòu)或某個(gè)值,如何獲取到呢?一直困惑著我們,不過本文將為大家解開疑惑,感興趣的朋友可以了解下,或許本文對(duì)你有所幫助2013-02-02JQuery使用屬性addClass、removeClass和toggleClass實(shí)現(xiàn)增加和刪除類操作示例
這篇文章主要介紹了JQuery使用屬性addClass、removeClass和toggleClass實(shí)現(xiàn)增加和刪除類操作,涉及jquery事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作相關(guān)使用技巧,需要的朋友可以參考下2019-11-11jQuery實(shí)現(xiàn)的粘性滾動(dòng)導(dǎo)航欄效果實(shí)例【附源碼下載】
這篇文章主要介紹了jQuery實(shí)現(xiàn)的粘性滾動(dòng)導(dǎo)航欄效果,涉及jQuery插件smint的相關(guān)使用技巧,并附帶完整實(shí)例源碼供讀者下載參考,需要的朋友可以參考下2017-10-10用jquery的方法制作一個(gè)簡(jiǎn)單的導(dǎo)航欄
用jquery制作一個(gè)簡(jiǎn)單的導(dǎo)航欄,使用到了addClass及removeClass等方法,需要的朋友可以參考下2014-06-06