jQuery源碼解析之clone()

前言:這篇講完後,jQuery的文檔處理就告一段落了,有空我把這部分整合下,發一篇文章目錄。javascript

1、示例代碼php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>jQuery源碼解析之clone()</title>
</head>
<body>
<script src="jQuery.js"></script>

<div id="divTwo">
  <p id="pTwo">這是divTwo
    <span id="spanTwo">這是spanTwo</span>
  </p>
</div>
<div id="divOne">

</div>

<script>
  $("#pTwo").click(function ({
    alert("pTwo被點擊了")
  })
  $("#spanTwo").click(function (e{
    //阻止冒泡
    e.stopPropagation()
    alert("spanTwo被點擊了")
  })
  // $("#pTwo").clone().appendTo($("#divOne"))
  // $("#pTwo").clone(true,false).appendTo($("#divOne"))
  $("#pTwo").clone(true,true).appendTo($("#divOne"))
</script>
</body>
</html>
複製代碼

2、$().clone()
做用:
生成被選元素的副本,包含子節點、文本和屬性html

注意:
$('div').clone(true) 表示克隆目標節點的事件和數據
$('div').clone(true,true) 表示克隆目標節點及其子節點的事件和數據java

源碼:node

jQuery.fn.extend({
    //克隆目標節點及其子節點
    //dataAndEvents是否克隆目標節點的事件和數據,默認是false
    //deepDataAndEvents是否克隆目標節點子節點的事件和數據,默認值是dataAndEvents
    //源碼6327行
    clone: function( dataAndEvents, deepDataAndEvents {
      //默認是false
      dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
      //默認是dataAndEvents
      deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
      //循環調用jQuery.clone
      return this.map( function({
        return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
      } );
    },
});
複製代碼

解析:
能夠看到,這裏仍是比較簡單的,須要注意的就是參數deepDataAndEvents不填的話,其值是根據參數dataAndEvents的值來定的git

3、jQuery.clone()
做用同上github

源碼:markdown

jQuery.extend( {
    //源碼6117行
    //生成被選元素的副本,包含子節點、文本和屬性
    clonefunction( elem, dataAndEvents, deepDataAndEvents ) {
      var i, l, srcElements, destElements,
        //拷貝目標節點的屬性和值
        //若是爲true,則包括拷貝子節點的全部屬性和值
        clone = elem.cloneNode( true ),
        //判斷elem是否脫離文檔流
        inPage = jQuery.contains( elem.ownerDocument, elem );

      // Fix IE cloning issues
      //兼容性處理,解決IE bug
      if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
        !jQuery.isXMLDoc( elem ) ) {

        // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
        destElements = getAll( clone );
        srcElements = getAll( elem );

        for ( i = 0, l = srcElements.length; i < l; i++ ) {
          fixInput( srcElements[ i ], destElements[ i ] );
        }
      }

      // Copy the events from the original to the clone
      //從目標節點克隆事件,並綁定給克隆的元素
      if ( dataAndEvents ) {
        //克隆子節點的事件和數據
        if ( deepDataAndEvents ) {
          //源節點
          srcElements = srcElements || getAll( elem );
          //克隆節點
          destElements = destElements || getAll( clone );

          for ( i = 0, l = srcElements.length; i < l; i++ ) {
            cloneCopyEvent( srcElements[ i ], destElements[ i ] );
          }
        }
        //只克隆目標節點和數據
        else {
          cloneCopyEvent( elem, clone );
        }
      }

      //將script標籤設爲已運行
      // Preserve script evaluation history
      destElements = getAll( clone"script" );
      if ( destElements.length > 0 ) {
        setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
      }

      // Return the cloned set
      return clone;
    },
})
複製代碼

解析:
能夠看到這部分源碼主要分爲三大塊:
(1)解決 IE 的 bug,主要是在fixInput()方法上進行處理
(2)從目標節點克隆數據、添加事件給克隆的元素
(3)將克隆的元素中的script標籤設爲已運行app

4、fixInput()
做用:
(1)解決 IE 沒法保存克隆的單選、多選的狀態的 bug
(2)解決 IE 沒法將克隆的選項返回至默認選項狀態的 bugjsp

源碼:

  //解決IE的bug:(1)沒法保存克隆的單選、多選的狀態 (2)沒法將克隆的選項返回至默認選項狀態
  // Fix IE bugs, see support tests
  //源碼5937行
  function fixInput( src, dest {
    var nodeName = dest.nodeName.toLowerCase();

    // Fails to persist the checked state of a cloned checkbox or radio button.
    //IE沒法保存克隆的單選框和多選框的選擇狀態
    if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
      dest.checked = src.checked;
    }
    //IE沒法將克隆的選項返回至默認選項狀態
    // Fails to return the selected option to the default selected state when cloning options
    else if ( nodeName === "input" || nodeName === "textarea" ) {
      dest.defaultValue = src.defaultValue;
    }
  }
複製代碼

解析:
本質就是將目標元素的checked屬性defaultValue屬性手動賦值給克隆的元素。

5、cloneCopyEvent()
做用:
$().clone()的關鍵方法,用來從目標節點克隆數據、添加事件給克隆的元素

注意:
jQuery 採用數據分離的方法來保存 DOM 上的事件和數據,利用 uuid 標記每一個 DOM 元素,而後在內存上,將每一個 DOM 元素相關的數據放到內存中,而後在 uuid 和內存的數據之間創建映射。

優勢是方便複製數據。

注意:事件是不可賦值的,只能一個個添加!

示意圖:

源碼:

 //src:目標元素
  //dest:克隆的元素
  //源碼5902行
  function cloneCopyEvent( src, dest {
    var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;

    if ( dest.nodeType !== 1 ) {
      return;
    }
    //拷貝jQuery內部數據:事件、處理程序等
    // 1. Copy private data: events, handlers, etc.
    if ( dataPriv.hasData( src ) ) {
      //private data old,即目標元素的數據
      //注意:jQuery是經過uuid將目標元素進行標記,
      //而後將與目標元素相關的數據都放到內存中
      //經過uuid和內存的數據創建映射
      //這種數據分離的作法有利於複製數據,但不能複製事件
      pdataOld = dataPriv.access( src );
      //private data current,即爲克隆的元素設置數據
      pdataCur = dataPriv.set( dest, pdataOld );
      events = pdataOld.events;
      //若是事件存在
      if ( events ) {
        //移除克隆對的元素的處理程序和事件
        delete pdataCur.handle;
        pdataCur.events = {};
        //依次爲克隆的元素添加事件
        //注意:事件是不能被複制的,因此須要從新綁定
        for ( type in events ) {
          for ( i = 0, l = events[ type ].length; i < l; i++ ) {
            jQuery.event.add( dest, type, events[ type ][ i ] );
          }
        }
      }
    }

    // 2. Copy user data
    //拷貝用戶數據
    if ( dataUser.hasData( src ) ) {
      udataOld = dataUser.access( src );
      udataCur = jQuery.extend( {}, udataOld );
      //爲克隆的元素設置數據
      dataUser.set( dest, udataCur );
    }
  }
複製代碼

解析:
(1)拷貝 jQuery 內部數據(事件、處理程序)
拷貝賦值數據:

pdataOld = dataPriv.access( src );
//private data current,即爲克隆的元素設置數據
pdataCur = dataPriv.set( dest, pdataOld );
複製代碼

拷貝添加事件:

jQuery.event.add( dest, type, events[ type ][ i ] );
複製代碼

(2)拷貝用戶數據

dataUser.set( dest, udataCur );
複製代碼

6、setGlobalEval()
做用:
設置目標元素內部的<script>標籤爲已執行

源碼:

  //設置目標元素內部的`<script>`標籤爲已執行
  //源碼4934行
  // Mark scripts as having already been evaluated
  function setGlobalEval( elems, refElements {
    var i = 0,
      l = elems.length;

    for ( ; i < l; i++ ) {
      dataPriv.set(
        elems[ i ],
        "globalEval",
        !refElements || dataPriv.get( refElements[ i ], "globalEval" )
      );
    }
  }
複製代碼

Github:
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索