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 纠正 cleanWhitespace函数
Mar 11 Javascript
javascript 四则运算精度修正函数代码
May 31 Javascript
阻止子元素继承父元素事件具体思路及实现
May 02 Javascript
JS实现FLASH幻灯片图片切换效果的方法
Mar 04 Javascript
JavaScript中使用concat()方法拼接字符串的教程
Jun 06 Javascript
数据结构中的各种排序方法小结(JS实现)
Jul 23 Javascript
基于jQuery实现文字打印动态效果
Apr 21 jQuery
动态创建Angular组件实现popup弹窗功能
Sep 15 Javascript
微信小程序倒计时功能实现代码
Nov 09 Javascript
详解如何使用nvm管理Node.js多版本
May 06 Javascript
在Vue中创建可重用的 Transition的方法
Jun 02 Javascript
vue props 一次传多个值实例
Jul 22 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网站基础优化方法小结
2008/09/29 PHP
php Http_Template_IT类库进行模板替换
2009/03/19 PHP
PHP验证码类代码( 最新修改,完全定制化! )
2010/12/02 PHP
py文件转exe时包含paramiko模块出错解决方法
2016/08/12 PHP
PHP实现二叉树深度优先遍历(前序、中序、后序)和广度优先遍历(层次)实例详解
2018/04/20 PHP
thinkPHP5框架auth权限控制类与用法示例
2018/06/12 PHP
jQuery/CSS3图片特效插件整理推荐
2014/12/07 Javascript
node.js中的querystring.escape方法使用说明
2014/12/10 Javascript
jquery实现实时改变网页字体大小、字体背景色和颜色的方法
2015/08/05 Javascript
Bootstrap每天必学之基础排版
2015/11/20 Javascript
JS实现添加,替换,删除节点元素的方法
2016/06/30 Javascript
Bootstrap 轮播(Carousel)插件
2016/12/26 Javascript
JS字符串长度判断,超出进行自动截取的实例(支持中文)
2017/03/06 Javascript
JavaScript中一些特殊的字符运算
2017/08/17 Javascript
ECMAscript 变量作用域总结概括
2017/08/18 Javascript
fetch 使用及如何接收JS传值
2017/11/11 Javascript
详解Vue + Vuex 如何使用 vm.$nextTick
2017/11/20 Javascript
vue前端框架—Mint UI详解(更适用于移动端)
2019/04/30 Javascript
js对象简介与基本用法示例
2020/03/13 Javascript
JS如何实现封装列表右滑动删除收藏按钮
2020/07/23 Javascript
Vue记住滚动条和实现下拉加载的完美方法
2020/07/31 Javascript
jQuery实现评论模块
2020/08/19 jQuery
[03:54]Ehome出征西雅图 回顾2016国际邀请赛晋级之路
2016/08/02 DOTA
python中的__slots__使用示例
2015/02/26 Python
详解Python网络爬虫功能的基本写法
2016/01/28 Python
Python中装饰器学习总结
2018/02/10 Python
python 处理string到hex脚本的方法
2018/10/26 Python
Django框架 查询Extra功能实现解析
2019/09/04 Python
HTML5单页面手势滑屏切换原理分析
2017/07/10 HTML / CSS
新西兰第一的行李箱网站:luggage.co.nz
2019/07/22 全球购物
另类冲刺标语
2014/06/24 职场文书
卫生院艾滋病宣传活动小结
2014/07/09 职场文书
会议开幕词
2015/01/28 职场文书
起诉状范本
2015/05/20 职场文书
民事辩护词范文
2015/05/21 职场文书
中学总务处工作总结
2015/08/12 职场文书