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的更多相关文章
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- jQuery源码分析系列(转载来源Aaron.)
声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ...
- jQuery源码分析系列——来自Aaron
jQuery源码分析系列——来自Aaron 转载地址:http://www.cnblogs.com/aaronjs/p/3279314.html 版本截止到2013.8.24 jQuery官方发布最新 ...
- jQuery-1.9.1源码分析系列完毕目录整理
jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...
- jquery2源码分析系列
学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...
- [转] jQuery源码分析-如何做jQuery源码分析
jQuery源码分析系列(持续更新) jQuery的源码有些晦涩难懂,本文分享一些我看源码的方法,每一个模块我基本按照这样的顺序去学习. 当我读到难度的书或者源码时,会和<如何阅读一本书> ...
- MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
随机推荐
- 移动开发可能用到的css单位
众所周知CSS技术我们虽然很熟悉,在使用的过程却很容易被困住,这让我们在新问题出现的时候变得很不利.随着web继续不断地发展,对于新技术新 解决方案的要求也会不断增长.因此,作为网页设计师和前端开发人 ...
- io.js的服務器突破
Node.js与io.js那些事儿 InfoQ中文站 05月20日 14:26 去年12月,多位重量级Node.js开发者不满Joyent对Node.js的管理,自立门户创建了io.js.io.js的 ...
- Windows系统上的.Net版本和.NETFramework的C#版本
前言 注:本文内容摘自维基百科,用于在墙内时当作笔记看. WinForm 需要.Net最低版本 2.0 WPF需要的.Net最低版本 3.0 (Win7及之上版本自带) C#版本 版本 语言规格 日期 ...
- (新年快乐)ABP理论学习之本地化(2016第一篇)
返回总目录 本篇目录 应用语言 本地化资源 获取本地化文本 扩展本地化资源 最佳实践 应用语言 一个应用至少有一种UI语言,许多应用不止有一种语言.ABP为应用提供了一个灵活的本地化系统. 第一件事情 ...
- 玩转JavaScript OOP[0]——基础类型
前言 long long ago,大家普遍地认为JavaScript就是做一些网页特效的.处理一些事件的.我身边有一些老顽固的.NET程序员仍然停留在这种认知上,他们觉得没有后端开发肯定是构建不了系统 ...
- jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)
Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...
- XCode的个人使用经验
Xcode是强大的IDE(但个人觉得不如Visual Studio做得好),其强大功能无需本人再赘述,本文也不是一篇“快捷键列表”,因为XCode上的快捷键极其多,而且还有不少是需要同时按下四个按键的 ...
- .NET垃圾回收(GC)原理
作为.NET进阶内容的一部分,垃圾回收器(简称GC)是必须了解的内容.本着“通俗易懂”的原则,本文将解释CLR中垃圾回收器的工作原理. 基础知识 托管堆(Managed Heap) 先来看MSDN的解 ...
- JavaScript css-dom
HTML负责结构层,网页的结构层由HTML或者XHTML之类的标记语言负责构建 CSS负责表示层,描述页面内容应该如何呈现. JavaScript负责行为层,负责内容应该如何响应事件这一问题. 能利用 ...
- Razor Engine,实现代码生成器的又一件利器
Razor Engine,之前仅仅是ASP.NET MVC的一种View引擎,目前已经完全成为一种可以独立使用的模版引擎,并且已经成为了CodePlex上一个开源的项目(http://razoreng ...