jQuery-1.9.1源码分析系列(二)jQuery选择器续

在分析之前说一点题外话。

ownerDocument和 documentElement的区别

  ownerDocument是Node对象的一个属性,返回的是某个元素的根节点文档对象:即document对象;documentElement是Document对象的属性,返回的是文档根节点

  对于HTML文档来说,documentElement是<html>标签对应的Element对象,ownerDocument是document对象.

接下开始正题。

1.几个jQuery选择器源码中遇到的几个函数


a. 解析函数:jQuery.parseHTML/parseJSON/parseXML函数详解



jQuery.parseHTML( data[, context][, keepScripts] ):将字符串解析成DOM节点集合

  这个函数本身并不复杂。首先data必须是有意义字符串,然后参数纠正,因为后面两个参数都是可选的。

  if ( !data || typeof data !== "string" ) {

    return null;

  }

  if ( typeof context === "boolean" ) {

    keepScripts = context;

    context = false;

  }

  然后根据data的格式分两种情况处理:

  第一种:data是单个纯标签的情况,比如“<p></p>”或“<input/>”或“<input >”,则创建标签后组装成数组返回即可

  //rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/

  var parsed = rsingleTag.exec( data );

  if ( parsed ) {

    return [ context.createElement( parsed[1] ) ];

  }

  第二种:其他情况,使用 jQuery.buildFragment创建DOM节点碎片(包裹data创建出来的DOM节点)组装成数组返回。需要注意keepScripts参数规定是否保留其中的script标签,默认为false。

  scripts = !keepScripts && [];

  

  parsed = jQuery.buildFragment( [ data ], context, scripts );

  if ( scripts ) {
    jQuery( scripts ).remove();
  }
  return jQuery.merge( [], parsed.childNodes );

  里面用到了jQuery.buildFragment,这个才是parseHTML的核心。

创建文档片段核心函数jQuery.buildFragment( elems, context, scripts, selection )详解

  首先,创建安全的创建文档碎片节点

  safe = createSafeFragment( context );

  所谓的安全,指的实际上是IE低版本兼容问题。createSafeFragment函数的源码如下

  function createSafeFragment( document ) {

    var list = nodeNames.split( "|" ),

    safeFrag = document.createDocumentFragment(); // ie6,7,8浏览器把safeFrage作为HTMLDocument类型
      

     // 在IE6-8中添加HTML5新标签中的一个hack,具体作用?我也没搞明白

    if ( safeFrag.createElement ) {

  while ( list.length ) {

        safeFrag.createElement(list.pop());

      }

    }

    return safeFrag;

  }

  var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|"

    + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video";

  可以看出如果浏览器支持safeFrag.createElement的情况下(ie低版本),是不支持nodeNames中的标签的,需要使用createElement来一个个创建,具体有神马作用,请知情人士告知,谢谢。

  至于动态创建html节点的方法document.createDocumentFragment,还有其他相关方法,有兴趣的童鞋可以查一下资料:

  · crateAttribute(name):        用指定名称name创建特性节点

  · createComment(text):       创建带文本text的注释节点

  · createDocumentFragment():    创建文档碎片节点

  · createElement(tagname):       创建标签名为tagname的节点

  · createTextNode(text):         创建包含文本text的文本节点

  然后:收集节点元素

  遍历elems参数,对每一个元素elem生成的节点压入节点缓存nodes中。

  对每一个elem 的处理分三种情况:

  1)jQuery.type( elem ) === "object" //直接添加节点

    jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

  2)!rhtml.test( elem )//非”<…”或”&…;”这类html元素直接当文本节点处理

nodes.push( context.createTextNode( elem ) );

  3)字符串html ;这种情况使用innerHTML将elem添加到文档碎片节点safe下的DIV标签中,然后使用DIV.childNodes把所有子节点压入节点缓存nodes即可。原理是简单,但是。。。兼容是个大问题。这里面有几个兼容问题需要解决

  在低版本IE下,某些标签必须要包含在一些标签内,比如”<thead>”标签必须要在”<table>”内。

  jQuery特意把所有这类情况保存在wrapMap中,wrapMap为(嵌套层数,起始标签,终止标签)wrapMap = {

    option: [ 1, "<select multiple=‘multiple‘>", "</select>" ],

    legend: [ 1, "<fieldset>", "</fieldset>" ],

    area: [ 1, "<map>", "</map>" ],

    param: [ 1, "<object>", "</object>" ],

    thead: [ 1, "<table>", "</table>" ],

    tr: [ 2, "<table><tbody>", "</tbody></table>" ],

    col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],

    td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

    // IE6-8 不能正常加载 link, script, style, or any html5 (NoScope) 标签,除非把他包含在一个非中断字符后面的div中.

    _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]

}

  拿到elem先判断第一个标签名称,如果能在wrapMap中找到对应的属性,则用wrapMap中的外标签包裹起来,比如elem="<thead><tr></tr></thead>"处理后变成lem="<table><thead><tr></tr></thead></table>"。处理源码如下

  tmp = tmp || safe.appendChild( context.createElement("div") );

// rtagName :/<([\w:]+)/;获取标签名

tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();/

wrap = wrapMap[ tag ] || wrapMap._default;

  // rxhtmlTag: /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi。

  //对非单个可闭合如“div”这样的标签误用为“<div#F/>”这样的闭合方式改成“<div#F></div>”

  tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];

  

  这样创建文档碎片是可以了,但是我们要把elem对应的文档取出来的时候不能包括我们添加上的外包装。这部分处理我们结合源码看一下

  //将tmp定位到真正的elem内容部分的父节点,到时候直接使用tmp.childNodes即可

  j = wrap[0];
  while ( j-- ) {
    tmp = tmp.lastChild;
  }

  //rleadingWhitespace = /^\s+/
  //IE会将文本中的开始空格给删掉,比如$("   <span></span>")在IE上表现和$("<span></span>")一样,span前面的三个空格被干掉了。要把它找回来
  if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
    nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
  }

  // IE在创建table碎片时会自动添加<tbody>标签
  if ( !jQuery.support.tbody ) {

    //rtbody = /<tbody/i;设置elem为<table...</table>,用来在后面去掉tbody
    //elem最外层标签是<table>, 并且<tbody>是IE自己添加上去的
    elem = tag === "table" && !rtbody.test( elem ) ?
    tmp.firstChild :
    //elem是裸的<thead>或<tfoot>,会自动添加<table>和<tbody>
    wrap[1] === "<table>" && !rtbody.test( elem ) ?
    tmp :
    0;

    //去掉<tbody>
    j = elem && elem.childNodes.length;
    while ( j-- ) {
      if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
        elem.removeChild( tbody );
      }
    }
  }

  jQuery.merge( nodes, tmp.childNodes );//收集节点

  //循环使用的数据恢复初始值,以备后用

  tmp.textContent = "";

  // Fix #12392 for oldIE
  while ( tmp.firstChild ) {
    tmp.removeChild( tmp.firstChild );
  }

  tmp = safe.lastChild;

  OK,到此,搜集节点元素完成。不要忘了最后需要将文档碎片节点添加的DIV标签删掉。

  最后:构建碎片文档

  遍历每一个元素节点放入碎片文档中,safe.appendChild( elem )

  while ( (elem = nodes[ i++ ]) ) {

    // #4087 -如果起点和终点的元素是相同的,而且这是该元素,什么也不做;在DOM选取操作中用到

    if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
      continue;
    }

    contains = jQuery.contains( elem.ownerDocument, elem );

    //添加节点到文档碎片中,并搜集script标签
    tmp = getAll( safe.appendChild( elem ), "script" );

    //保存脚本执行记录
    if ( contains ) {
      setGlobalEval( tmp );
    }

    //捕获脚本,将脚本都保存到scripts中
    if ( scripts ) {
      j = 0;
      while ( (elem = tmp[ j++ ]) ) {
        //rscriptType = /^$|\/(?:java|ecma)script/i
        if ( rscriptType.test( elem.type || "" ) ) {
          scripts.push( elem );
        }
      }
    }
  }

  return safe;//返回

jQuery.parseJSON( data ):将格式完好的JSON字符串转为与之对应的JavaScript对象

  所谓"格式完好",就是要求指定的字符串必须符合严格的JSON格式,例如:属性名称必须加双引号、字符串值也必须用双引号。如果传入一个格式不"完好"的JSON字符串将抛出一个JS异常。

  功能比较点单如果能使用window.JSON.parse来解析则直接使用。

  if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); }

  否则使用( new Function( "return " + data ) )()来解析

   return ( new Function( "return " + data ) )();

  完整源码如下:

  parseJSON: function( data ) {

    if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); }// 尝试使用浏览器的JSON.parse来解析

    if ( data === null ) { return data; }

    if ( typeof data === "string" ) {

  data = jQuery.trim( data );//去掉头尾空格(IE不能处理他)

      if ( data ) {

        // 确保data是严格的JSON格式,从http://json.org/json2.js借逻辑

        //rvalidchars = /^[\],:{}\s]*$/,
        //rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
        //rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
        //rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,

  if ( rvalidchars.test( data.replace( rvalidescape, "@" ).replace( rvalidtokens, "]" ).replace( rvalidbraces, "")) ) {

    return ( new Function( "return " + data ) )();

  }

  }

    }

    jQuery.error( "Invalid JSON: " + data );

  }

jQuery.parseXML( data ):将字符串解析为对应的XML文档

  该函数将使用浏览器内置的解析函数来创建一个有效的XML文档,该文档可以传入jQuery()函数来创建一个典型的jQuery对象,从而对其进行遍历或其他操作.

  这个比较简单,偷懒直接附上源码:

  parseXML: function( data ) {

    var xml, tmp;
    if ( !data || typeof data !== "string" ) {
      return null;
    }
    try {
      if ( window.DOMParser ) { // Standard
        tmp = new DOMParser();
        xml = tmp.parseFromString( data , "text/xml" );
      } else { // IE
        xml = new ActiveXObject( "Microsoft.XMLDOM" );
        xml.async = "false";
        xml.loadXML( data );
      }
    } catch( e ) {
      xml = undefined;
    }
    if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
      jQuery.error( "Invalid XML: " + data );
    }
    return xml;
  }

时间: 2024-08-05 19:33:15

jQuery-1.9.1源码分析系列(二)jQuery选择器续的相关文章

Cordova Android源码分析系列二(CordovaWebView相关类分析)

本篇文章是Cordova Android源码分析系列文章的第二篇,主要分析CordovaWebView和CordovaWebViewClient类,通过分析代码可以知道Web网页加载的过程,错误出来,多线程处理等. CordovaWebView类分析 CordovaWebView类继承了Android WebView类,这是一个很自然的实现,共1000多行代码.包含了PluginManager pluginManager,BroadcastReceiver receiver,CordovaInt

Redis java客户端 jedis 源码分析系列二:单实例 jedis

在使用Jedis的过程中最简单的用法就是单实例单连接的jedis,如下代码所示: public void testJedis(){ Jedis jedis = new Jedis("127.0.0.1"); jedis.set("key", "value"); jedis.get("key"); jedis.close(); } 让我们深入到内部去看一看其结构,如下图所示: 此处请先忽略 JedisPool 类和 Pool&l

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTransport, 3.类型转换器 ajaxConvert 源码结构: jQuery.extend({ /** * 前置过滤器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 请求分发器 *

jQuery源码分析系列(38) : 队列操作

Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Queue队列 队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除. 为什么要引入队列? 我们知道代码的执行流有异步与同步之分,例如 var a

jQuery源码分析系列(36) : Ajax - 类型转化器

什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的响应只有 responseText与responseXML 二种 所以现在我要定义dataType为jsonp,那么所得的最终数据是一个json的键值对,所以jQuery内部就会默认帮你完成这个转化工作 jQuery为了处理这种执行后数据的转化,就引入了类型转化器,如果没有指定类型就依据响应头Con

jQuery源码分析系列(34) : Ajax - 预处理jsonp

上一章大概讲了前置过滤器和请求分发器的作用,这一章主要是具体分析每种对应的处理方式 $.ajax()调用不同类型的响应,被传递到成功处理函数之前,会经过不同种类的预处理(prefilters). 预处理的类型取决于由更加接近默认的Content-Type响应,但可以明确使用dataType选项进行设置.如果提供了dataType选项, 响应的Content-Type头信息将被忽略. 有效的数据类型是text, html, xml, json,jsonp,和 script. dataType:预期

jQuery源码分析系列(39) : 动画队列

data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保存队列里面的的动画数据 所以我们在自习回顾下关于数据缓存 //These may be used throughout the jQuery core codebase //存数据的 //用户使用 data_user = new Data(); //存储对象 //jQuery内部私有 //用来存事件

jQuery源码分析系列(35) : Ajax - jsonp的实现与原理

ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本 json核心就是:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了. jquery ext dojo这类库的实现手段其实大同小异 在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img.iframe.s

jQuery源码分析系列(37) : Ajax 总结

综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery. ajaxTransport 类型转换器 ajaxConvert 为了整体性与扩展性考虑,把整个结构通过Deferred实现异步链式模型,Promise对象可以轻易的绑定成功.失败.进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了 出于同源策略考虑,存在跨域问题,所以ajax内部

jQuery源码分析系列(31) : Ajax deferred实现 - Aaron.

AJAX的底层实现都是浏览器提供的,所以任何基于api上面的框架或者库,都只是说对于功能的灵活与兼容维护性做出最优的扩展 ajax请求的流程: 1.通过 new XMLHttpRequest 或其它的形式(指IE)生成ajax的对象xhr. 2.通过xhr.open(type, url, async, username, password)的形式建立一个连接. 3.通过setRequestHeader设定xhr的请求头部(request header). 4.通过send(data)请求服务器端