jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点


Posted in Javascript onDecember 01, 2015

什么情况下使用到克隆节点?

我们知道在对DOM操作过程中如果直接使用节点会出现节点随操作而变动的情况。比如对节点使用.after/.before/.append等方法后,节点被添加到新的地方,原来的位置上的节点被移除了。有的时候需要保留原来位置上的节点,仅仅是需要一个副本添加到对应位置,这个时候克隆就有了使用场景。

jQuery.fn.clone克隆当前匹配元素集合的一个副本,并以jQuery对象的形式返回。

你还可以指定是否复制这些匹配元素(甚至它们的子元素)的附加数据( data()函数 )和绑定事件。

jQueyr.fn.clone: function( withDataAndEvents, deepDataAndEvents )参数描述

a.克隆函数的底层实现步骤分解如下(jQuery.clone)

第一步,先克隆出DOM节点。对支持正确的节点克隆(即支持elem.cloneNode并保证克隆无误)的DOM节点直接使用cloneNode(true),否则自建一个节点来保存被克隆数据然后获取该节点。

if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
  clone = elem.cloneNode( true );
// IE<=8 不能正确克隆已分离、未知的节点
//直接新建一个相同的节点,然后获取
} else {
//fragmentDiv是全局变量
  fragmentDiv.innerHTML = elem.outerHTML;
  fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
}

第二步,如果是IE浏览器下,则需要通过fixCloneNodeIssues( node, destElements[i] );来逐个修正IE克隆问题。IE克隆解决方案全部包含在了fixCloneNodeIssues中,下一节详细分析。里面的jQuery.support内容点击这里查看更多

//针对ie克隆问题修正
if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
  (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
  //在这里我们不使用Sizzle的原因是: http://jsperf.com/getall-vs-sizzle/2
  destElements = getAll( clone );
  srcElements = getAll( elem );
  //修正所有IE克隆问题
  for ( i = 0; (node = srcElements[i]) != null; ++i ) {
    // Ensure that the destination node is not null; Fixes #9587
    if ( destElements[i] ) {
      fixCloneNodeIssues( node, destElements[i] );
    }
  }
}

 

第三步,如果要克隆缓存数据(包括普通数据和绑定事件),克隆之。

//克隆绑定的事件
if ( dataAndEvents ) {
  if ( deepDataAndEvents ) {
    srcElements = srcElements || getAll( elem );
    destElements = destElements || getAll( clone );
    for ( i = 0; (node = srcElements[i]) != null; i++ ) {
      cloneCopyEvent( node, destElements[i] );
    }
  } else {
    cloneCopyEvent( elem, clone );
  }
}

备注:cloneCopyEvent函数中会将原节点的数据保存到克隆节点中,然后将原节点的事件绑定到新的克隆节点上

function cloneCopyEvent( src, dest ) {
    if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
      return;
    }
    var type, i, l,
    oldData = jQuery._data( src ),
    curData = jQuery._data( dest, oldData ),//dest是克隆对的节点
    events = oldData.events;
    if ( events ) {
      //保证被克隆的节点的事件对象干净,确保没有后面添加的事件没有重复
      delete curData.handle;
      curData.events = {};
      for ( type in events ) {
        for ( i = 0, l = events[ type ].length; i < l; i++ ) {
          jQuery.event.add( dest, type, events[ type ][ i ] );
        }
      }
    }
    // 使克隆的数据对象化
    if ( curData.data ) {
      curData.data = jQuery.extend( {}, curData.data );
    }
  }

第四步,保护script计算历史(全局性地标记scripts代码段已经被执行过了),并回收内存,返回克隆节点。

destElements = getAll( clone, "script" );
if ( destElements.length > 0 ) {
  setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
}
destElements = srcElements = node = null;
return clone;

b.IE克隆问题汇总fixCloneNodeIssues(src,dest)

src是原节点,dest是src的克隆节点。

IE克隆问题列一下(IE8+)

1.IE6-8当使用cloneNode会克隆事件(这些事件绑定通过attachEvent)。为保证统一性,需要清除克隆的事件,为后续统一克隆事件做准备

// IE6-8当使用cloneNode复制事件(这些事件绑定通过attachEvent)时进入该分支
  //清除原来的事件,为克隆事件做准备
  if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
    data = jQuery._data( dest );
    for ( e in data.events ) {
      jQuery.removeEvent( dest, e, data.handle );
    }
    dest.removeAttribute( jQuery.expando );
  }

2.IE8-克隆脚本标签script的时候克隆的内容结果会是空白。我们需要给他重新赋值,并确保他不会执行脚本内容。

//IE克隆脚本时内容为空白,并试图执行新设置的文本
  if ( nodeName === "script" && dest.text !== src.text ) {
    disableScript( dest ).text = src.text;
    restoreScript( dest );
    }

3.IE6-10不能克隆使用的classid获取的对象元素的子节点。IE10下,如果父节点为null,则会抛出NoModificationAllowedError异常。需要使用原节点的outerHTML和innerHTML重新赋值。

//IE6-10不能克隆使用的classid获取的对象元素的子节点。
  //IE10下,如果父节点为null,则会抛出NoModificationAllowedError异常
  else if ( nodeName === "object" ) {
    if ( dest.parentNode ) {
      dest.outerHTML = src.outerHTML;
    }
    //对于IE9,这个条分支不可避免。
    //IE9中克隆对象元素,上述outerHTML策略是不充分的。
    //如果src具有的innerHTML并且克隆节点却没有,
    //复制src.innerHTML到dest.innerHTML #10324
    if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
      dest.innerHTML = src.innerHTML;
    }
  }

4.IE6-8无法克隆一个复选框或单选按钮的选中状态。需要主动设置。

// manipulation_rcheckableType = /^(?:checkbox|radio)$/i
  else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
    //IE6-8无法坚持一个克隆的复选框或单选按钮的选中状态
    //更糟的是,如果defaultChecked值没有设置,则IE6-7无法给克隆元素选中状态的外观
    dest.defaultChecked = dest.checked = src.checked;
    ...
  }

5.当克隆select标签时,IE6-8无法正确返回select默认选中状态。需要主动设置。

 //当克隆选项时,IE6-8无法正确返回select默认选中状态
   else if ( nodeName === "option" ) {
    dest.defaultSelected = dest.selected = src.defaultSelected;
   }

6.当克隆其他类型的input和textare标签时,IE6-8不能正确设置defaultValue为正确的值。需要主动设置。

//当克隆其他类型的input标签时,IE6-8不能正确设置defaultValue为正确的值
  else if ( nodeName === "input" || nodeName === "textarea" ) {
    dest.defaultValue = src.defaultValue;
  }

里面用到disableScript这个函数。函数目的是改变script的type,从而保证在给script赋值后不会被作为脚本执行。这个方式我们可以借鉴

//为安全DOM操作替换/保存script节点元素type属性
function disableScript( elem ) {
  var attr = elem.getAttributeNode("type");
  elem.type = ( attr && attr.specified ) + "/" + elem.type;
  return elem;
}

以上内容是小编给大家介绍的关于jQuery-1.9.1源码分析系列(十一)DOM操作续之克隆节点的全部叙述,希望大家喜欢。

Javascript 相关文章推荐
显示、隐藏密码
Jul 01 Javascript
再次更新!MSClass (Class Of Marquee Scroll通用不间断滚动JS封装类 Ver 1.6)
Feb 05 Javascript
弹出广告特效代码(一个IP只弹出一次)
May 11 Javascript
Jquery工作常用实例 使用AJAX使网页进行异步更新
Jul 26 Javascript
javascript笛卡尔积算法实现方法
Apr 08 Javascript
javascript实现粘贴qq截图功能(clipboardData)
May 29 Javascript
全面解析DOM操作和jQuery实现选项移动操作代码分享
Jun 07 Javascript
JavaScript表单验证完美代码
Mar 02 Javascript
原生JS实现导航下拉菜单效果
Nov 25 Javascript
vue.js计算属性computed用法实例分析
Jul 06 Javascript
uni-app之APP和小程序微信授权方法
May 09 Javascript
微信小程序获取位置展示地图并标注信息的实例代码
Sep 01 Javascript
快速学习jQuery插件 Cookie插件使用方法
Dec 01 #Javascript
快速学习jQuery插件 jquery.validate.js表单验证插件使用方法
Dec 01 #Javascript
JavaScript使用DeviceOne开发实战(二) 生成调试安装包
Dec 01 #Javascript
JavaScript使用DeviceOne开发实战(一) 配置和起步
Dec 01 #Javascript
快速学习jQuery插件 Form表单插件使用方法
Dec 01 #Javascript
jQuery学习笔记之Ajax用法实例详解
Dec 01 #Javascript
易操作的jQuery表单提示插件
Dec 01 #Javascript
You might like
显示程序执行时间php函数代码
2013/08/29 PHP
浅谈PHP中的错误处理和异常处理
2017/02/04 PHP
实例讲解php将字符串输出到HTML
2019/01/27 PHP
基于jquery实现的鼠标滑过按钮改变背景图片
2011/07/15 Javascript
Js注册协议倒计时的小例子
2013/06/24 Javascript
js下拉菜单语言选项简单实现
2013/09/23 Javascript
用JavaScript实现类似于ListBox功能示例代码
2014/03/09 Javascript
Node.js和PHP根据ip获取地理位置的方法
2014/03/14 Javascript
jQuery内容过滤选择器用法分析
2015/02/10 Javascript
JavaScript实现列表分页功能特效
2015/05/15 Javascript
jQuery中的ajax async同步和异步详解
2015/09/29 Javascript
js clearInterval()方法的定义和用法
2015/11/11 Javascript
JavaScript中的this机制
2016/01/30 Javascript
Bootstrap精简教程中秋大放送
2016/09/15 Javascript
jQuery validate插件功能与用法详解
2016/12/15 Javascript
快速了解Node中的Stream流是什么
2019/02/13 Javascript
vue element中axios下载文件(后端Python)
2019/05/10 Javascript
[05:09]DOTA2-DPC中国联赛2月22日Recap集锦
2021/03/11 DOTA
python计算书页码的统计数字问题实例
2014/09/26 Python
使用wxPython获取系统剪贴板中的数据的教程
2015/05/06 Python
Python中Django 后台自定义表单控件
2017/03/28 Python
Python 实现数据库更新脚本的生成方法
2017/07/09 Python
Python判断一个list中是否包含另一个list全部元素的方法分析
2018/12/24 Python
Python mutiprocessing多线程池pool操作示例
2019/01/30 Python
Django Python 获取请求头信息Content-Range的方法
2019/08/06 Python
使用CSS3制作版头动画效果
2020/12/24 HTML / CSS
法国娇韵诗官方旗舰店:Clarins是来自法国的天然护肤品牌
2018/06/30 全球购物
Made in Design英国:设计家具、照明、家庭装饰和花园家具
2019/09/24 全球购物
高中数学教学反思
2014/01/30 职场文书
电子专业自荐信
2014/07/01 职场文书
文明好少年事迹材料
2014/08/19 职场文书
县政府办公室领导班子个人对照检查材料
2014/09/16 职场文书
2014年学生资助工作总结
2014/12/18 职场文书
钱学森观后感
2015/06/04 职场文书
详解Python描述符的工作原理
2021/06/11 Python
Golang 切片(Slice)实现增删改查
2022/04/22 Golang