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

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

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.选择器结构 jQuery的选择器根据源码可以分为几块 init: function( selector, context, rootjQuery ) { ... // HANDLE: $(""), $(null), $(undefined), $(false) ... // ...
  • 前面分析了选择器的结构和几个解析函数,接下来分析jQuery对象的伪类选择器.这里所谓的jQuery对象的伪类选择器就是从已有的jQuery对象(元素集合)中筛选出指定的集合出来. 4.    jQuery的伪类选择函数 先混个脸熟,把所有能找到的jQuery的伪类选择器都列出来. jQuery.f ...
  • 这一节主要是jQuery中最基础的几个东东 2.    jQuery的几个基础属性和函数 a. jQuery.noConflict函数详解 在jQuery初始化的时候保存了外部的$和jQuery _jQuery = window.jQuery, _$ = window.$, noConflict函数 ...
  • jQuery-1.9.1源码分析系列三Sizzle选择器引擎——编译原理续伪类分割器setMatcher
    我也看过很多Sizzle源码分析的博客,伪类分割器setMatcher介绍的比较少.但是本人认为这是一个比较重要的难点,我第一遍看源码的时候也忽略了.现在回来看第二遍,一定要把这个东东弄懂. a. 伪类分割器setMatcher 伪类分隔器对伪类选择器进行分隔处理,返回处理后的最终匹配器. 伪类和其 ...
  • Sizzle引擎的主体部分已经分析完毕了,今天为这部分划一个句号. a. Sizzle解析流程总结 是时候该做一个总结了.Sizzle解析的流程已经一目了然了. 1.选择器进入Sizzle( selector, context, results, seed )函数,先对选择器不符合要求的(比如没有选 ...
  • jQuery-1.9.1源码分析系列三Sizzle选择器引擎——编译原理
    这一节要分析的东东比较复杂,篇幅会比较大,也不知道我描述后能不能让人看明白.这部分的源码我第一次看的时候也比较吃力,现在重头看一遍,再分析一遍,看能否查缺补漏. 看这一部分的源码需要有一个完整的概念后去看才比较容易看懂,所以我们先把整个编译的原理阐述以后再进行解析. 还是以上次的那个CSS选择器为例 ...
  • 说一下Sizzle中零碎的API.这些API有的被jQuery接管,直接使用jQuery.xxx就可以使用,有的没有被接管,如果要在jQuery中使用,使用方法是jQuery.find.xxx. 具体看一下有哪些API //筛选出elements满足CSS选择器表达式expr的节点[最终返回的是节点 ...
  • jQuery-1.9.1源码分析系列三Sizzle选择器引擎——词法解析
    jQuery源码9600多行,而Sizzle引擎就独占近2000行,占了1/5.Sizzle引擎.jQuery事件机制.ajax是整个jQuery的核心,也是jQuery技术精华的体现.里面的有些策略确实很值得学习,先膜拜之,然后细细学习. 在学习Sizzle引擎之前我们先准备一点知识,和先了解Si ...
一周排行
  • SVN技术交流提纲:http://lazio10000.github.io/tech/SVN/#/bored
  • 移动硬盘安装windowsXP与windows8系统
    哥拆笔记本无数,从未出现过什么问题,今天杯具发生了,而且居然是在自己笔记本上面,(由于这台 ...
  • 本文从系统管理员的角度讨论安全问题.系统管理员是管理系统的人:启动系统,停止系统运行,安装新软件,增加新用户,删除老用户,以及完成保持系统发展和运行的日常事务工作. 1.安全管理 安全管理主要分为四个方面: (1)防 ...
  •     linux curl是一个利用URL规则在命令行下工作的文件传输工具.它支持文件的上传和下载,所以是综合传输工具,但按传统,习惯称url为下载工具. 一,curl命令参数,有好多我没有用过,也不知道翻译的对不 ...
  • 什么是Silverlight
    本篇描述Silverlight技术的基本概念,编写,编译和发布,让你通过一个小例子,理解S ...
  • 在描述算法之前,先看看下面的5*5的表格: 1 3 4 10 11 2 5 9 12  19  6 8 13 18 20 7 14 17 21 24 15 16 22 23 25    上面的表格很容易看出规律.就是 ...
  • 下面来看看3.1的运行结果:运行结果:{ "_id" : { "$oid" : "4c2845d8735efe55298d0dc9"} , "na ...
  • 作者: 蒋宁,腾讯资深产品经理;手机腾讯网产品总监;侧重在无线互联网产品战略规划及产品经理团队培养工作     做团队管理和做业务不同,特别是面对一群高智商高素质的产品经理,需要一些策略和耐心:这期间也有一些感悟,也 ...
  • 1,Xlib: No protocol specified   设置xhost +允许所有的服务器使用当前的X Server,默认情况下,其他服务器是无法访问本机的X Server,这时如果执行需要图形化显示的命令, ...
  •  参考: iOS 静态库,动态库与 Framework Xcode: 给项目添加framework