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

ownerDocument和 documentElement的区别

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

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

接下开始正题。

3.几个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,IE6-8不支持html5标签,标签会被解析错误,先创建自定义标签然后使用就不会出现浏览器解析错误
    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来一个个创建,具体有神马作用,请点击IE兼容性问题汇总【持续更新中】中查看IE8-不支持自定义标签

  至于动态创建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;
  }

function getAll( context, tag )

  这是一个jQuery内部使用的函数,非常有用。他获取context中(自身以及后代节点)标签名为tag的节点集合。

  他使用context.getElementsByTagName或context.querySelectorAll来获取,当实在是没有获取到值的时候通过context.childNodes来获取conten的儿子节点中标签为tag的节点。

function getAll( context, tag ) {
var elems, elem,
i = 0,
// context为dom节点时直接获取
    found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
    typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) :
undefined; // context不为dom节点,为dom节点数组时,循环获取数组元素的每个子tag
if ( !found ) {
    for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
if ( !tag || jQuery.nodeName( elem, tag ) ) {
found.push( elem );
} else {
jQuery.merge( found, getAll( elem, tag ) );
}
}
} //如果传入节点context的节点名和tag相同,需要包含本身
return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
jQuery.merge( [ context ], found ) :
found;
}

jQuery.grep( elems, callback, inv )函数详解

  函数目的是过滤出用户指定的数据。一般来说第三个参数不传或传为false,callback是一个过滤器,过滤成功返回true,过滤失败返回false。最终grep函数将callback返回成功的素有elem元素返回。这个函数无论是在jQuery内部,或者我们自己使用都很有作用

grep: function( elems, callback, inv ) {
var retVal,
ret = [],
i = 0,
length = elems.length;
inv = !!inv; // Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
retVal = !!callback( elems[ i ], i );
if ( inv !== retVal ) {
ret.push( elems[ i ] );
}
}
return ret;
},

  如果觉得本文不错,请点击右下方【推荐】!

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

  1. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. jQuery源码分析系列(转载来源Aaron.)

    声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...

  5. jQuery源码分析系列——来自Aaron

    jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...

  6. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  7. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  8. [转] jQuery源码分析-如何做jQuery源码分析

    jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...

  9. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  10. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

随机推荐

  1. Linux 系统的初始化配置

      1.零时配置网卡IP地址 2.配置永久生效IP地址   需要进如 cd /etc/sysconfig/network-scripts  找到网卡文件编辑 3.零时主机名的更改. 4.永久主机名的更 ...

  2. Principles of measurement of sound intensity

    Introduction In accordance with the definition of instantaneous sound intensity as the product of th ...

  3. PostgreSQL 杂志

    pgmag 团队刚发布了头两期 PostgreSQL 杂志,还有中文版http://pgmag.org/download,推荐广大 PostgreSQL 数据库管理员及开发者阅读: Issue #01 ...

  4. ASP.Net WebForm温故知新学习笔记:二、ViewState与UpdatePanel探秘

    开篇:经历了上一篇<aspx与服务器控件探秘>后,我们了解了aspx和服务器控件背后的故事.这篇我们开始走进WebForm状态保持的一大法宝—ViewState,对其刨根究底一下.然后,再 ...

  5. 你必须知道的指针基础-4.sizeof计算数组长度与strcpy的安全性问题

    一.使用sizeof计算数组长度 1.1 sizeof的基本使用 如果在作用域内,变量以数组形式声明,则可以使用sizeof求数组大小,下面一段代码展示了如何使用sizeof: ,,,,,}; int ...

  6. QParserGenerator的文法文件介绍

    在沉默了数月之后,博主心血来潮想继续介绍QParserGenerator,在这里我们将不再继续介绍任何有关于LALR(1)的算法(那东西只会把你的脑子变成一团浆糊),让我们来看一下QParserGen ...

  7. 入手Invicta 8926 OB潜水自动机械腕表

    前个月前就想入手一款手表了,之前在关注和学习.询问他人选哪样的表好,前些天还在看精工Seiko机械表系列,今凌晨有朋友给我推荐这款Invicta 8926系列手表,我一看便喜欢了. 在网上也是搜索了很 ...

  8. Spring学习记录(七)---表达式语言-SpEL

    SpEL---Spring Expression Language:是一个支持运行时查询和操作对象图表达式语言.使用#{...}作为定界符,为bean属性动态赋值提供了便利. ①对于普通的赋值,用Sp ...

  9. CSS学习笔记2-2d变换和过渡属性

    前言:今天又是一个周末,心情不错,趁着闲暇之余,把剩下来的CSS3学习的内容全部整理出来,练习用的源码也稍微整理了一下. 2D转换 transform:translate||rotate||scale ...

  10. .net 网络编程

    1.首先说下计算机网络中的TCP/IP参考模型 TCP/IP把网络分为5层,每一层负责完成不同的功能 1)应用层:传输报文,提供各种网络应用,有FTP.SMTP.HTTP等协议 2)运输层:传输报文段 ...