Jquery-1.9.1源码分析系列(十一)之DOM操作

DOM操作包括append、prepend、before、after、replaceWith、appendTo、prependTo、insertBefore、insertAfter、replaceAll。其核心处理函数是domManip。

  DOM操作函数中后五种方法使用的依然是前面五种方法,源码

jQuery.each({
    appendTo: "append",
    prependTo: "prepend",
    insertBefore: "before",
    insertAfter: "after",
    replaceAll: "replaceWith"
  }, function( name, original ) {
    jQuery.fn[ name ] = function( selector ) {
      var elems,
      i = 0,
      ret = [],
      insert = jQuery( selector ),
      last = insert.length - 1;
      for ( ; i <= last; i++ ) {
        elems = i === last ? this : this.clone(true);
        jQuery( insert[i] )[ original ]( elems );
        //现代浏览器调用apply会把jQuery对象当如数组,但是老版本ie需要使用.get()
        core_push.apply( ret, elems.get() );
      }
      return this.pushStack( ret );
    };
  });

  浏览器原生的插入节点的方法有两个:appendChild和inserBefore,jQuery利用这两个方法拓展了如下方法

  jQuery.fn.append使用this.appendChild( elem )

  jQuery.fn.prepend使用this.insertBefore( elem, this.firstChild )

  jQuery.fn.before使用this.parentNode.insertBefore( elem, this );

  jQuery.fn.after使用this.parentNode.insertBefore( elem, this.nextSibling );

  jQuery.fn.replaceWith 使用this.parentNode.insertBefore( elem, this.nextSibling);

  看一个例子的源码(jQuery.fn.append)

  append: function() {
      return this.domManip(arguments, true, function( elem ) {
        if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
          this.appendChild( elem );
        }
      });
    }

  根据上面的源码。猜测domManip的作用是遍历当前jQuery对象所匹配的元素,然后每个元素调用传入的回调,并将要插入的节点(如果是字符串那么需要创建文档碎片节点)作为传入的回调的参数;并执行传入的回调。

  接下来分析domManip,看猜测是否正确。dom即Dom元素,Manip是Manipulate的缩写,连在一起的字面意思就是就是Dom操作。

a. domManip: function( args, table, callback )解析

  args 待插入的DOM元素或HTML代码

  table 是否需要修正tbody,这个变量是优化的结果

  callback 回调函数,执行格式为callback.call( 目标元素即上下文, 待插入文档碎片/单个DOM元素 )

  先看流程,再看细节

  第一步,变量初始化。其中iNoClone在后面会用到,如果当前的jQuery对象所匹配的元素不止一个(n > 1)的话,意味着构建出来的文档碎片需要被n用到,则需要被克隆(n-1)次,加上碎片文档本身才够n次使用;value 是第一个参数args的第一个元素,后面会对value是函数做特殊处理;

var first, node, hasScripts,
  scripts, doc, fragment,
  i = 0,
  l = this.length,
  set = this,
  iNoClone = l - 1,
  value = args[0],
  isFunction = jQuery.isFunction( value );

  第二步,处理特殊下要将当前jQuery对象所匹配的元素一一调用domManip。这种特殊情况有两种:第一种,如果传入的节点是函数(即value是函数)则需要当前jQuery对象所匹配的每个元素都将函数计算出的值作为节点代入domManip中处理。第二种,webkit下,我们不能克隆文含有checked的文档碎片;克隆的文档不能重复使用,那么只能是当前jQuery对象所匹配的每个元素都调用一次domManip处理。

//webkit下,我们不能克隆文含有checked的档碎片
if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
  return this.each(function( index ) {
    var self = set.eq( index );
    //如果args[0]是函数,则执行函数返回结果替换原来的args[0]
    if ( isFunction ) {
      args[0] = value.call( this, index, table ? self.html() : undefined );
    }
    self.domManip( args, table, callback );
  });
}

  第三步,处理正常情况,使用传入的节点构建文档碎片,并插入文档中。这里面构建的文档碎片就需要重复使用,区别于第二步的处理。这里面需要注意的是如果是script节点需要在加载完成后执行。顺着源码顺序看一下过程

  构建文档碎片

fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
first = fragment.firstChild;
if ( fragment.childNodes.length === 1 ) {
  fragment = first;
}

  分离出其中的script,这其中有一个函数disableScript更改了script标签的type值以确保安全,原来的type值是"text/javascript",改成了"true/text/javascript"或"false/text/javascript"

scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
hasScripts = scripts.length;

  文档碎片插入页面

for ( ; i < l; i++ ) {
  node = fragment;
  if ( i !== iNoClone ) {
    node = jQuery.clone( node, true, true );
    // Keep references to cloned scripts for later restoration
    if ( hasScripts ) {
      jQuery.merge( scripts, getAll( node, "script" ) );
    }
  }
  callback.call(
    table && jQuery.nodeName( this[i], "table" ) ?
    findOrAppend( this[i], "tbody" ) :
    this[i],
    node,
    i
    );
}

  执行script,分两种情况,远程的使用ajax来处理,本地的直接执行。

if ( hasScripts ) {
  doc = scripts[ scripts.length - 1 ].ownerDocument;
  // Reenable scripts
  jQuery.map( scripts, restoreScript );
  //在第一个文档插入使执行可执行脚本
  for ( i = 0; i < hasScripts; i++ ) {
    node = scripts[ i ];
    if ( rscriptType.test( node.type || "" ) &&
      !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
      if ( node.src ) {
        // Hope ajax is available...
        jQuery.ajax({
          url: node.src,
          type: "GET",
          dataType: "script",
          async: false,
          global: false,
          "throws": true
        });
      } else {
        jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
      }
    }
  }
}

b. dom操作拓展

jQuery.fn.text

jQuery.fn.text: function( value ) {
  return jQuery.access( this, function( value ) {
    return value === undefined ?
    jQuery.text( this ) :
    this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
  }, null, value, arguments.length );
}

  最终执行value === undefined ? jQuery.text( this ) : this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );

  其中jQuery.text = Sizzle.getText;

jQuery.fn.html

  函数使用jQuery.access来处理

  jQuery.fn.html: function( value ) {
      return jQuery.access( this, function( value ) {...}, null, value, arguments.length );
    }

  如果没有参数表示是取值

if ( value === undefined ) {
  return elem.nodeType === 1 ?
  elem.innerHTML.replace( rinlinejQuery, "" ) :
  undefined;
}

  否则看是否能用innerHTML添加内容。点击参考兼容问题

//看看我们是否可以走了一条捷径,只需使用的innerHTML
//需要执行的代码script|style|link等不能使用innerHTML
//htmlSerialize:确保link节点能使用innerHTML正确序列化,这就需要在IE浏览器的包装元素
//leadingWhitespace:IE strips使用.innerHTML需要以空白开头
//不是需要额外添加结束标签或外围包装标签的元素
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
  ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
  ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
  !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
  value = value.replace( rxhtmlTag, "<$1></$2>" );
  try {
    for (; i < l; i++ ) {
        //移除元素节点和缓存,阻止内存泄漏
        elem = this[i] || {};
        if ( elem.nodeType === 1 ) {
          jQuery.cleanData( getAll( elem, false ) );
          elem.innerHTML = value;
        }
      }
      elem = 0;
    //如果使用innerHTML抛出异常,使用备用方法
  } catch(e) {}
}

  如果不能使用innerHTML或使用不成功(抛出异常),则使用备用方法append

//备用方法,使用append添加节点
if ( elem ) {
  this.empty().append( value );
}
  
jQuery.fn.wrapAll(用单个标签将所有匹配元素包裹起来)
   处理步骤:
  传入参数是函数则将函数结果传入
if ( jQuery.isFunction( html ) ) {
  return this.each(function(i) {
    jQuery(this).wrapAll( html.call(this, i) );
  });
}
  创建包裹层
//获得包裹标签 The elements to wrap the target around
var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
if ( this[0].parentNode ) {
  wrap.insertBefore( this[0] );
}

  用包裹裹住当前jQuery对象

wrap.map(function() {
  var elem = this;
  while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
    elem = elem.firstChild;
  }
   return elem;
}).append( this );

  注意:当前jQuery对象匹配的元素最好只有一个,如果有多个的话不推荐使用,这种情况慎用,后面举例可以看到。

  简单的例子,原DOM为(后面都使用这个例子)

<div id='center' class="center">
  <div id='ss' class="center">
    <input type='submit' id='left' class="left">
  </div>
</div>
<div class="right">我是right</div>
  $('#center').wrapAll("<p></p>")后,dom变成了
<p>
  <div id="center" class="center">
    <div id="ss" class="center">
      <input type="submit" id="left" class="left">
    </div>
  </div>
</p>
<div class="right">我是right</div>

  慎用:如果当前jQuery所匹配的元素不止一个,例如原DOM执行$('div').wrapAll(“<p></p>”)后结果DOM变成

<p>
  <div id="center" class="center"></div>
  <div id="ss" class="center">
    <input type="submit" id="left" class="left">
  </div>
  <div class="right">我是right</div>
</p>

  看到结果了吧,本来#center是#ss的父节点,结果变成了#ss的兄弟节点。

jQuery.fn.wrapInner(在每个匹配元素的所有子节点外部包裹指定的HTML结构)

  处理步骤:

  传入参数是函数则将函数结果传入

if ( jQuery.isFunction( html ) ) {
  return this.each(function(i) {
    jQuery(this).wrapInner( html.call(this, i) );
  });
}

  遍历jQuery对象数组,获取每个元素包含的内容(所有子节点)contents,然后使用warpAll包裹住contents

return this.each(function() {
  var self = jQuery( this ),
  contents = self.contents();

  if ( contents.length ) {
    contents.wrapAll( html );

  } else {
    self.append( html );
  }
});

  还是使用上面的例子中的原DOM,执行$('div').wrapInner('<p></p>')后结果DOM变成

<div id="center" class="center">
  <p>
    <div id="ss" class="center">
      <p>
        <input type="submit" id="left" class="left">
      </p>
    </div>
  </p>
</div>
<div class="right">
  <p>
    我是right
  </p>
</div>

jQuery.fn.wrap(在每个匹配元素外部包裹指定的HTML结构)

  对jQuery的每个元素分别使用wrapAll包裹一下

return this.each(function(i) {
  jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
});

行$('div').wrap('<p></p>')后结果DOM变成

<p>
  <div id="center" class="center">
    <p>
      <div id="ss" class="center">
        <input type="submit" id="left" class="left">
      </div>
    </p>
  </div>
</p>
<p>
  <div class="right">我是right</div>
</p>

jQuery.fn.unwrap(移除每个匹配元素的父元素)

  使用replaceWith用匹配元素父节点的所有子节点替换匹配元素的父节点。当然了父节点是body/html/document肯定是移除不了的

return this.parent().each(function() {
  if ( !jQuery.nodeName( this, "body" ) ) {
    jQuery( this ).replaceWith( this.childNodes );
  }
}).end();
  执行$('div').wrap()后结果DOM变成
<div id="ss" class="center">
  <input type="submit" id="left" class="left">
</div>
<div class="right">我是right</div>

  

jQuery.fn.remove(从文档中移除匹配的元素)

  你还可以使用选择器进一步缩小移除的范围,只移除当前匹配元素中符合指定选择器的部分元素。

  与detach()相比,remove()函数会同时移除与元素关联绑定的附加数据( data()函数 )和事件处理器等(detach()会保留)。

for ( ; (elem = this[i]) != null; i++ ) {
  if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) {
    // detach传入的参数keepData为true,不删除缓存
    if ( !keepData && elem.nodeType === 1 ) {
      //清除缓存
      jQuery.cleanData( getAll( elem ) );
    }
    if ( elem.parentNode ) {
      if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
        setGlobalEval( getAll( elem, "script" ) );
      }
      elem.parentNode.removeChild( elem );
    }
  }
}

  可以看到其中有一个重要的函数cleanData,该方法是用来清除缓存:遍历每一个节点元素,对每一个节点元素做一下处理:

1.获取当前元素对应的缓存

id = elem[ internalKey ];
data = id && cache[ id ];

2.如果有绑定事件,则遍历解绑事件

if ( data.events ) {
  for ( type in data.events ) {
    if ( special[ type ] ) {
      jQuery.event.remove( elem, type );
    //这是一个快捷方式,以避免jQuery.event.remove的开销
    } else {
      jQuery.removeEvent( elem, type, data.handle );
    }
  }
}

3.如果jQuery.event.remove没有移除cache,则手动移除cache。其中IE需要做一些兼容处理,而且最终会将删除历史保存如core_deletedIds中

//当jQuery.event.remove没有移除cache的时候,移除cache
if ( cache[ id ] ) {
  delete cache[ id ];
  //IE不允许从节点使用delete删除expando特征,
  //也能对文件节点使用removeAttribute函数;
  //我们必须处理所有这些情况下,
  if ( deleteExpando ) {
    delete elem[ internalKey ];
  } else if ( typeof elem.removeAttribute !== core_strundefined ) {
    elem.removeAttribute( internalKey );
  } else {
    elem[ internalKey ] = null;
  }
  core_deletedIds.push( id );
}

jQuery.fn.detach

detach: function( selector ) {
      return this.remove( selector, true );
    },

jQuery.fn.empty(清空每个匹配元素内的所有内容(所有子节点))

  函数将会移除每个匹配元素的所有子节点(包括文本节点、注释节点等所有类型的节点),会清空相应的缓存数据。

for ( ; (elem = this[i]) != null; i++ ) {
  //防止内存泄漏移除元素节点缓存
  if ( elem.nodeType === 1 ) {
    jQuery.cleanData( getAll( elem, false ) );
  }
  //移除所有子节点
  while ( elem.firstChild ) {
    elem.removeChild( elem.firstChild );
  }
  // IE<9,select节点需要将option置空
  if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
    elem.options.length = 0;
  }
}
(0)

相关推荐

  • jQuery-1.9.1源码分析系列(十)事件系统之事件包装

    在上篇文章给大家介绍了jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构,本篇继续给大家介绍jquery1.9.1源码分析系列相关知识,具体内容请看下文吧. 首先需要明白,浏览器的原生事件是只读的,限制了jQuery对他的操作.举个简单的例子就能明白为什么jQuery非要构造一个新的事件对象. 在委托处理中,a节点委托b节点在a被click的时候执行fn函数.当事件冒泡到b节点,执行fn的时候上下文环境需要保证正确,是a节点执行了fn而非b节点.如何保证执行fn的上下文环境是a节点

  • jQuery1.9.1针对checkbox的调整方法(prop)

    在jquery 1.8.x中的版本,我们对于checkbox的选中与不选中操作如下: 判断是否选中 复制代码 代码如下: $('#checkbox').prop('checked') 设置选中与不选中状态: 复制代码 代码如下: $('#checkbox').attr('checked',true)$('#checkbox').attr('checked',false) 但此方法在jquery1.9.1中,有些处理不一样 IE10,Chrome,FF中,对于选中状态,第一次$('#checkbo

  • Jquery AJAX 框架的使用方法

    刚开始学了 JQuqery, 众多的 $get(),...等等符号早已把我搞晕了.暂时就放弃了.后来学习 ASP.NET AJAX ,在微软的领导下,逐渐由服务器端转向客户端编程. 激起我客户端编程的兴趣,才想起学习一下了 Jquery.随着WEB2.0及ajax思想在互联网上的快速发展传播,陆续出现了一些优秀的Js框架,其中比较著名的有Prototype.YUI.jQuery.mootools.Bindows以及国内的JSVM框架等,通过将这些JS框架应用到我们的项目中能够使程序员从设计和书写

  • jquery 框架使用教程 AJAX篇

    小试了下AJAX,感觉比prototype简洁多了,在JQuery中,AJAX有三种实现方式:$.ajax(),$.post,$.get(). XHTML(主要): <div id="result" style="background:orange;border:1px solid red;width:300px;height:400px;"></div> <form id="formtest" action=&qu

  • jQuery 1.9.1源码分析系列(十三)之位置大小操作

    先给大家展示谢 jQuery.fn.css (propertyName [, value ]| object )(函数用于设置或返回当前jQuery对象所匹配的元素的css样式属性值.如果需要删除指定的css属性,请使用该函数将其值设为空字符串("") 注意:1.如果省略了value参数,则表示获取属性值:如果指定了该参数,则表示设置属性值.2.css()函数的所有"设置"操作针对的是当前jQuery对象所匹配的每一个元素:所有"读取"操作只针对

  • jQuery 1.9.1源码分析系列(十四)之常用jQuery工具

    为了给下一章分析动画处理做准备,先来看一下一些工具.其中队列工具在动画处理中被经常使用. jQuery.fn. queue(([ queueName ] [, newQueue ]) || ([ queueName ,] callback ))(获取或设置当前匹配元素上待执行的函数队列. 如果当前jQuery对象匹配多个元素:获取队列时,只获取第一个匹配元素上的队列:设置队列(替换队列.追加函数)时,则为每个匹配元素都分别进行设置.如果需要移除并执行队列中的第一个函数,请使用dequeue()函

  • javascript之AJAX框架使用说明

    ajax编码落地算是团队框架建设的一个里程碑,使得框架拥有了一个可应用调试的版本.在ajax框架编码中主要想到了这么几点: 框架兼容性 方法全面而要简单快捷 可扩展性好 框架兼容性:ajax框架的兼容性主要表现在XMLHttpRquest对象生成上.根据不同类型浏览器或者不同库一般有三种方式 new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP"): new ActiveXObject("Msxml2.XMLHT

  • jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构

    又是一个重磅功能点. 在分析源码之前分析一下体系结构,有助于源码理解.实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了:而且jQuery事件系统的设计思想也是基于该思想的,所以我们先分析一下Dean Edwards前辈的事件绑定. a. jQuery事件原型--Dean Edwards的跨浏览器AddEvent()设计 源码解读 //事件添加方法 function addEvent(element, type, handler) { //

  • 基于JQuery框架的AJAX实例代码

    index.html 复制代码 代码如下: <html> <head> <title>jQuery Ajax 实例演示</title> </head> <script src="./js/jquery.js" type="text/javascript"></script> <script type="text/javascript"> $(docum

  • 简单的前端js+ajax 购物车框架(入门篇)

    今天在公司实在没有事做,突然就想到写下商城的购物车的前端框架,当然我这里只有购物车的增删改查,也许写的并不是那么完善,但最重要的是一个入门,也希望js达人给些建议,好让我更上一个台阶. HOHO~~~开始咯: Js: 复制代码 代码如下: //为了省事,就没写自己的js ajax了 用了jquery的,当然你也可以添加到jquery的扩展方法内,哈哈,我太懒了,所以就写这里了. var _$ = { AJAX: function (urlparm, d, beforecall, successc

  • 零基础学习AJAX之AJAX框架

    上文(零基础学习AJAX之AJAX的简介和基础)对ajax异步请求服务器做了详细的介绍和基础应用,可以看出,ajax的一些过程是相对不变的.不必要每次发送请求都写一遍发送代码,一些ajax开发人员已经把他们的过程封装成ajax框架. 本节主要介绍ajaxLib和ajaxGold两款框架. 1.ajaxLib ajaxLib是一个非常小巧的ajax框架. 使用它首先将文件使用引入到页面中.改框架是一个直接获取XML的框架,调运函数如下: loadXMLDoc(url,callback,boolea

  • jQuery 1.9.1源码分析系列(十)事件系统之绑定事件

    事件绑定的方法有很多种,使用了jquery那么原理那种绑定方式(elem.click = function(){...}))就不太想推荐给大家了.最主要的原因是elem.click=fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果. 下面给大家介绍jquery绑定事件的方式有哪些吧. 复制代码 代码如下: jQuery.fn.eventType([[data,] fn]) 比如eventType指的是事件类型,比如click: $("#chua").click(f

  • Jquery1.9.1源码分析系列(六)延时对象应用之jQuery.ready

    还记不记得jQuery初始化函数jQuery.fn.init中有这样是一个分支 //document ready简便写法$(function(){-}) } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } 所以$(fn)===$(document).ready(fn). 来看一下jQuery.fn.ready的源码 ready: function( fn ) { // Add t

  • AJAX 验证框架13个

    一 ASP.NET AJAX Framework 1.ValidatorCallout 是ASP.NET AJAX Control Toolkit中的一员,老瓶装新酒,通过扩展的方式为ASP.NET的验证控件提供漂亮的弹出提示效果.缺点是:目前的版本还不能提供服务端Custom Validator的扩展. 同时,ValidatorCallout还支持自定义CSS样式,具体可参考官方页面上的"ValidatorCallout Theming" 一节,和这篇文章:http://www.im

  • jQuery1.9.1源码分析系列(十六)ajax之ajax框架

    AJAX 简介 AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术. 您应当具备的基础知识 在继续学习之前,您需要对下面的知识有基本的了解: HTML / XHTML CSS JavaScript / DOM 如果您希望首先学习这些项目,请在我们的首页访问这些教程. 什么是 AJAX ? AJAX = 异步 JavaScript 和 XML. AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重

  • asp.net省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例

    本文主要列举了省市三级联动的DropDownList+Ajax的三种框架(aspnet/Jquery/ExtJs)示例.前段时间需要作一个的Web前端应用,需要用多个框架,一个典型的应用场景是省市三级联动,基于此应用,特将三种主要的ajax框架略作整理,方便有需要的朋友查阅. 在示例之前,我们先设置一个演示数据源,新建一个项目,项目结构如图: 主要文件如下:AreaModel.cs: 复制代码 代码如下: using System; using System.Collections.Generi

随机推荐