jQuery原始碼解析之clone()

進擊的小進進發表於2019-04-25

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

一、示例程式碼

<!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>
複製程式碼

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

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

原始碼:

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的值來定的

三、jQuery.clone()
作用同上

原始碼:

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標籤設為已執行

四、fixInput()
作用:
(1)解決 IE 無法儲存克隆的單選、多選的狀態的 bug
(2)解決 IE 無法將克隆的選項返回至預設選項狀態的 bug

原始碼:

  //解決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屬性手動賦值給克隆的元素。

五、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 );
複製程式碼

六、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…


(完)

相關文章