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 相关文章推荐
javascript的内存管理详解
Aug 07 Javascript
jQuery的文档处理程序详解
May 10 Javascript
浅析JavaScript Array和string的转换(推荐)
May 20 Javascript
JavaScript中的子窗口与父窗口的互相调用问题
Feb 08 Javascript
JavaScript实现一个空中避难的小游戏
Jun 06 Javascript
详解刷新页面vuex数据不消失和不跳转页面的解决
Jan 30 Javascript
解决angularJS中input标签的ng-change事件无效问题
Sep 13 Javascript
详解Node.js中path模块的resolve()和join()方法的区别
Oct 29 Javascript
Vue 组件参数校验与非props特性的方法
Feb 12 Javascript
Vue中axios的封装(报错、鉴权、跳转、拦截、提示)
Aug 20 Javascript
vue动态绘制四分之三圆环图效果
Sep 03 Javascript
Vue 实现监听窗口关闭事件,并在窗口关闭前发送请求
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
简介WordPress中用于获取首页和站点链接的PHP函数
2015/12/17 PHP
WHOOPS PHP调试库的使用
2017/09/29 PHP
javascript算法题 求任意一个1-9位不重复的N位数在该组合中的大小排列序号
2012/07/21 Javascript
禁止ajax缓存获取程序最新数据的方法
2013/11/19 Javascript
jquery中交替点击事件的实现代码
2014/02/14 Javascript
js 模式窗口(模式对话框和非模式对话框)的使用介绍
2014/07/17 Javascript
jQuery实现自动调整字体大小的方法
2015/06/15 Javascript
JS声明式函数与赋值式函数实例分析
2016/12/13 Javascript
Node.js安装配置图文教程
2017/05/10 Javascript
解决vue组件中使用v-for出现告警问题及v for指令介绍
2017/11/11 Javascript
微信小程序scroll-view组件实现滚动动画
2018/01/31 Javascript
详解React中setState回调函数
2018/06/14 Javascript
jQuery实现的3D版图片轮播示例【滑动轮播】
2019/01/18 jQuery
微信小程序实现人脸识别登陆的示例代码
2019/04/02 Javascript
解决Layui 表格自适应高度的问题
2019/11/15 Javascript
python使用7z解压apk包的方法
2015/04/18 Python
Python的Flask框架中实现登录用户的个人资料和头像的教程
2015/04/20 Python
配置 Pycharm 默认 Test runner 的图文教程
2018/11/30 Python
对Python3 解析html的几种操作方式小结
2019/02/16 Python
Python实现读取txt文件中的数据并绘制出图形操作示例
2019/02/26 Python
Django框架封装外部函数示例
2019/05/28 Python
python GUI图形化编程wxpython的使用
2019/07/19 Python
Django框架组成结构、基本概念与文件功能分析
2019/07/30 Python
python如果快速判断数字奇数偶数
2019/11/13 Python
python实现输出一个序列的所有子序列示例
2019/11/18 Python
python3 使用traceback定位异常实例
2020/03/09 Python
Python如何读取、写入CSV数据
2020/07/28 Python
给水排水工程专业毕业生推荐信
2013/10/28 职场文书
幼儿园教师培训方案
2014/02/04 职场文书
ktv总经理岗位职责
2014/02/17 职场文书
项目合作协议书范本
2014/04/16 职场文书
公司中层管理培训心得体会
2016/01/11 职场文书
Go缓冲channel和非缓冲channel的区别说明
2021/04/25 Golang
什么是SOLID
2022/03/24 Javascript
Golang 1.18 多模块Multi-Module工作区模式的新特性
2022/04/11 Golang
MySQL 原理与优化之原数据锁的应用
2022/08/14 MySQL