成人精品一区二区三区中文字幕-成人精品一区二区三区-成人精品一级毛片-成人精品亚洲-日本在线视频一区二区-日本在线视频免费

導航首頁 ? 技術教程 ? Jquery-1.9.1源碼分析系列(十一)之DOM操作
全站頭部文字 我要出現在這里
Jquery-1.9.1源碼分析系列(十一)之DOM操作 694 2024-03-19   

DOM操作包括append、prepend、before、after、replaceWith、appendTo、prependTo、insertBefore、insertAfter、replaceAll。其核心處理函數是domManip。

  DOM操作函數中后五種方法使用的依然是前面五種方法,源碼

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 );
        //現代瀏覽器調用apply會把jQuery對象當如數組,但是老版本ie需要使用.get()
        core_push.apply( ret, elems.get() );
      }
      return this.pushStack( ret );
    };
  });

  瀏覽器原生的插入節點的方法有兩個:appendChild和inserBefore,jQuery利用這兩個方法拓展了如下方法

  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);

  看一個例子的源碼(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 );
        }
      });
    }

  根據上面的源碼。猜測domManip的作用是遍歷當前jQuery對象所匹配的元素,然后每個元素調用傳入的回調,并將要插入的節點(如果是字符串那么需要創建文檔碎片節點)作為傳入的回調的參數;并執行傳入的回調。

  接下來分析domManip,看猜測是否正確。dom即Dom元素,Manip是Manipulate的縮寫,連在一起的字面意思就是就是Dom操作。

a. domManip: function( args, table, callback )解析

  args 待插入的DOM元素或HTML代碼

  table 是否需要修正tbody,這個變量是優化的結果

  callback 回調函數,執行格式為callback.call( 目標元素即上下文, 待插入文檔碎片/單個DOM元素 )

  先看流程,再看細節

  第一步,變量初始化。其中iNoClone在后面會用到,如果當前的jQuery對象所匹配的元素不止一個(n > 1)的話,意味著構建出來的文檔碎片需要被n用到,則需要被克?。╪-1)次,加上碎片文檔本身才夠n次使用;value 是第一個參數args的第一個元素,后面會對value是函數做特殊處理;

var first, node, hasScripts,
  scripts, doc, fragment,
  i = 0,
  l = this.length,
  set = this,
  iNoClone = l - 1,
  value = args[0],
  isFunction = jQuery.isFunction( value );

  第二步,處理特殊下要將當前jQuery對象所匹配的元素一一調用domManip。這種特殊情況有兩種:第一種,如果傳入的節點是函數(即value是函數)則需要當前jQuery對象所匹配的每個元素都將函數計算出的值作為節點代入domManip中處理。第二種,webkit下,我們不能克隆文含有checked的文檔碎片;克隆的文檔不能重復使用,那么只能是當前jQuery對象所匹配的每個元素都調用一次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]是函數,則執行函數返回結果替換原來的args[0]
    if ( isFunction ) {
      args[0] = value.call( this, index, table ? self.html() : undefined );
    }
    self.domManip( args, table, callback );
  });
}

  第三步,處理正常情況,使用傳入的節點構建文檔碎片,并插入文檔中。這里面構建的文檔碎片就需要重復使用,區別于第二步的處理。這里面需要注意的是如果是script節點需要在加載完成后執行。順著源碼順序看一下過程

  構建文檔碎片

fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) {
  fragment = first;
}

  分離出其中的script,這其中有一個函數disableScript更改了script標簽的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
    );
}

  執行script,分兩種情況,遠程的使用ajax來處理,本地的直接執行。

if ( hasScripts ) {
  doc = scripts[ scripts.length - 1 ].ownerDocument;
  // Reenable scripts
  jQuery.map( scripts, restoreScript );
  //在第一個文檔插入使執行可執行腳本
  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 );
}

  最終執行value === undefined ? jQuery.text( this ) : this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );

  其中jQuery.text = Sizzle.getText;

jQuery.fn.html

  函數使用jQuery.access來處理

  jQuery.fn.html: function( value ) {
      return jQuery.access( this, function( value ) {...}, null, value, arguments.length );
    }

  如果沒有參數表示是取值

if ( value === undefined ) {
  return elem.nodeType === 1 ?
  elem.innerHTML.replace( rinlinejQuery, "" ) :
  undefined;
}

  否則看是否能用innerHTML添加內容。點擊參考兼容問題

//看看我們是否可以走了一條捷徑,只需使用的innerHTML
//需要執行的代碼script|style|link等不能使用innerHTML
//htmlSerialize:確保link節點能使用innerHTML正確序列化,這就需要在IE瀏覽器的包裝元素
//leadingWhitespace:IE strips使用.innerHTML需要以空白開頭
//不是需要額外添加結束標簽或外圍包裝標簽的元素
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++ ) {
        //移除元素節點和緩存,阻止內存泄漏
        elem = this[i] || {};
        if ( elem.nodeType === 1 ) {
          jQuery.cleanData( getAll( elem, false ) );
          elem.innerHTML = value;
        }
      }
      elem = 0;
    //如果使用innerHTML拋出異常,使用備用方法
  } catch(e) {}
}

  如果不能使用innerHTML或使用不成功(拋出異常),則使用備用方法append

//備用方法,使用append添加節點
if ( elem ) {
  this.empty().append( value );
}
  
jQuery.fn.wrapAll(用單個標簽將所有匹配元素包裹起來)
   處理步驟:
  傳入參數是函數則將函數結果傳入
if ( jQuery.isFunction( html ) ) {
  return this.each(function(i) {
    jQuery(this).wrapAll( html.call(this, i) );
  });
}
  創建包裹層
//獲得包裹標簽 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] );
}

  用包裹裹住當前jQuery對象

wrap.map(function() {
  var elem = this;
  while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
    elem = elem.firstChild;
  }
   return elem;
}).append( this );

  注意:當前jQuery對象匹配的元素最好只有一個,如果有多個的話不推薦使用,這種情況慎用,后面舉例可以看到。

  簡單的例子,原DOM為(后面都使用這個例子)

<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>

  慎用:如果當前jQuery所匹配的元素不止一個,例如原DOM執行$('div').wrapAll(“<p></p>”)后結果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>

  看到結果了吧,本來#center是#ss的父節點,結果變成了#ss的兄弟節點。

jQuery.fn.wrapInner(在每個匹配元素的所有子節點外部包裹指定的HTML結構)

  處理步驟:

  傳入參數是函數則將函數結果傳入

if ( jQuery.isFunction( html ) ) {
  return this.each(function(i) {
    jQuery(this).wrapInner( html.call(this, i) );
  });
}

  遍歷jQuery對象數組,獲取每個元素包含的內容(所有子節點)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,執行$('div').wrapInner('<p></p>')后結果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(在每個匹配元素外部包裹指定的HTML結構)

  對jQuery的每個元素分別使用wrapAll包裹一下

return this.each(function(i) {
  jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
});

行$('div').wrap('<p></p>')后結果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(移除每個匹配元素的父元素)

  使用replaceWith用匹配元素父節點的所有子節點替換匹配元素的父節點。當然了父節點是body/html/document肯定是移除不了的

return this.parent().each(function() {
  if ( !jQuery.nodeName( this, "body" ) ) {
    jQuery( this ).replaceWith( this.childNodes );
  }
}).end();
  執行$('div').wrap()后結果DOM變成
<div id="ss" class="center">
  <input type="submit" id="left" class="left">
</div>
<div class="right">我是right</div>
  

jQuery.fn.remove(從文檔中移除匹配的元素)

  你還可以使用選擇器進一步縮小移除的范圍,只移除當前匹配元素中符合指定選擇器的部分元素。

  與detach()相比,remove()函數會同時移除與元素關聯綁定的附加數據( data()函數 )和事件處理器等(detach()會保留)。

for ( ; (elem = this[i]) != null; i++ ) {
  if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) {
    // detach傳入的參數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 );
    }
  }
}

  可以看到其中有一個重要的函數cleanData,該方法是用來清除緩存:遍歷每一個節點元素,對每一個節點元素做一下處理:

1.獲取當前元素對應的緩存

id = elem[ internalKey ];
data = id && cache[ id ];

2.如果有綁定事件,則遍歷解綁事件

if ( data.events ) {
  for ( type in data.events ) {
    if ( special[ type ] ) {
      jQuery.event.remove( elem, type );
    //這是一個快捷方式,以避免jQuery.event.remove的開銷
    } else {
      jQuery.removeEvent( elem, type, data.handle );
    }
  }
}

3.如果jQuery.event.remove沒有移除cache,則手動移除cache。其中IE需要做一些兼容處理,而且最終會將刪除歷史保存如core_deletedIds中

//當jQuery.event.remove沒有移除cache的時候,移除cache
if ( cache[ id ] ) {
  delete cache[ id ];
  //IE不允許從節點使用delete刪除expando特征,
  //也能對文件節點使用removeAttribute函數;
  //我們必須處理所有這些情況下,
  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(清空每個匹配元素內的所有內容(所有子節點))

  函數將會移除每個匹配元素的所有子節點(包括文本節點、注釋節點等所有類型的節點),會清空相應的緩存數據。

for ( ; (elem = this[i]) != null; i++ ) {
  //防止內存泄漏移除元素節點緩存
  if ( elem.nodeType === 1 ) {
    jQuery.cleanData( getAll( elem, false ) );
  }
  //移除所有子節點
  while ( elem.firstChild ) {
    elem.removeChild( elem.firstChild );
  }
  // IE<9,select節點需要將option置空
  if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
    elem.options.length = 0;
  }
}


主站蜘蛛池模板: 就爱小姐姐| 通往幸福的途径电影在线观看| 送老师锦旗写什么最好| 强者世界| 陈波儿| 爱四| 金鸳鸯| 柚子猫卡夫卡的心理暗示在线观看| 乔军| 意大利火环1990| 卢宇静| 红领章电视剧免费播放| 黑咖啡早上空腹喝还是饭后喝| 印度超人3| 李子京| 暴雪将至电影| bobo视频| 日本xxx.| 朱莉安妮全集高清免费| 电影喜宝| 标准体重| 大内群英 电视剧| 蓝眼泪简谱| 儿媳妇电视剧免费| 爱情公寓海报| 安东诺夫机场电影叫什么名字| 电影《stag》完整版| 寡妇激情| 冰雪十一天| 《ulises》完整版在线观看| 视频三级| 朋友的女友| 我不是教主漫画免费下拉式| 寻梦记| 视频欧美| 日日夜精品视频| 电影《此时此刻》| 三浦翔平| 多尔衮电视剧全集40集| 闺蜜之夏 电影| 诺亚方舟电影免费完整版在线观看|

!?。≌鹃L長期在線接!??!

網站、小程序:定制開發/二次開發/仿制開發等

各種疑難雜癥解決/定制接口/定制采集等

站長微信:lxwl520520

站長QQ:1737366103