国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 編程 > JavaScript > 正文

Jquery-1.9.1源碼分析系列(十一)之DOM操作

2019-11-20 11:10:25
字體:
供稿:網(wǎng)友

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用到,則需要被克隆(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;

  文檔碎片插入頁(yè)面

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 aroundvar 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í)候,移除cacheif ( 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;  }}
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 瑞金市| 汝城县| 永寿县| 江门市| 岳阳市| 苗栗县| 沙田区| 卢氏县| 门源| 东丽区| 承德县| 喀喇沁旗| 漳平市| 汾西县| 长葛市| 周口市| 大悟县| 兴山县| 长垣县| 岳阳市| 晋州市| 社旗县| 手游| 竹北市| 三台县| 芜湖县| 平远县| 陵水| 晋中市| 邵东县| 嘉祥县| 罗城| 高州市| 池州市| 长泰县| 泰兴市| 禄丰县| 勐海县| 梅河口市| 余江县| 尚志市|