jQuery源码解析之clone

7次阅读

共计 4739 个字符,预计需要花费 12 分钟才能阅读完成。

前言:这篇讲完后,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 行
    // 生成被选元素的副本,包含子节点、文本和属性
    clone: function(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:
https://github.com/AttackXiaoJinJin/jQueryExplain


(完)

正文完
 0