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 相关文章推荐
基于jquery的固定表头和列头的代码
May 03 Javascript
JavaScript基础篇之变量作用域、传值、传址的简单介绍与实例
Jun 29 Javascript
cookie的复制与使用记住用户名实现代码
Nov 04 Javascript
input禁止键盘及中文输入,但可以点击
Feb 13 Javascript
jQuery DOM操作实例
Mar 05 Javascript
基于socket.io和node.js搭建即时通信系统
Jul 30 Javascript
node.js中的http.response.end方法使用说明
Dec 14 Javascript
javascript:void(0)是什么意思及href=#与href=javascriptvoid(0)的区别
Nov 13 Javascript
node.js回调函数之阻塞调用与非阻塞调用
Nov 13 Javascript
详解vue-router传参的两种方式
Sep 10 Javascript
对 Vue-Router 进行单元测试的方法
Nov 05 Javascript
Vue的transition-group与Virtual Dom Diff算法的使用
Dec 09 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
thinkphp的CURD和查询方式介绍
2013/12/19 PHP
Apache启动报错No space left on device: AH00023该怎么解决
2015/10/16 PHP
Zend Framework数据库操作技巧总结
2017/02/18 PHP
PHP substr()函数参数解释及用法讲解
2017/11/23 PHP
PHP的mysqli_stmt_init()函数讲解
2019/01/24 PHP
php多进程并发编程防止出现僵尸进程的方法分析
2020/02/28 PHP
for 循环性能比较 提高for循环的效率
2009/03/19 Javascript
js中判断控件是否存在
2010/08/25 Javascript
扩展JavaScript功能的正确方法(译文)
2012/04/12 Javascript
JQuery操作单选按钮以及复选按钮示例
2013/09/23 Javascript
jQuery事件绑定与解除绑定实现方法
2015/04/15 Javascript
js全选按钮的实现方法
2015/11/17 Javascript
jQuery添加options点击事件并传值实例代码
2016/05/18 Javascript
easyui关于validatebox实现多重规则验证的方法(必看)
2017/04/12 Javascript
Vue中父子组件通讯之todolist组件功能开发
2018/05/21 Javascript
vue+webpack中配置ESLint
2018/11/07 Javascript
小程序getLocation需要在app.json中声明permission字段
2019/04/04 Javascript
layui复选框的全选与取消实现方法
2019/09/02 Javascript
Javascript原生ajax请求代码实例
2020/02/20 Javascript
keep-alive保持组件状态的方法
2020/12/02 Javascript
Python实现的朴素贝叶斯算法经典示例【测试可用】
2018/06/13 Python
python3 对list中每个元素进行处理的方法
2018/06/29 Python
印度购买眼镜和太阳镜网站:Coolwinks
2018/09/26 全球购物
eBay英国购物网站:eBay.co.uk
2019/06/19 全球购物
腾讯技术类校园招聘笔试试题
2014/05/06 面试题
毕业生文员求职信
2013/11/03 职场文书
财务方面个人工作的自我评价
2013/12/28 职场文书
会计职业生涯规划范文
2014/01/04 职场文书
护士辞职信模板
2014/01/20 职场文书
水电站项目建议书
2014/05/12 职场文书
党的群众路线教育实践活动个人对照检查材料(医生)
2014/11/05 职场文书
社团个人总结范文
2015/03/05 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
导游词之张家口
2019/12/13 职场文书
CSS几步实现赛博朋克2077风格视觉效果
2021/06/16 HTML / CSS
java实现自定义时钟并实现走时功能
2022/06/21 Java/Android