jQuery Sizzle选择器(三)
在Sizzle的入口方法Sizzle()中看到的一个根据浏览器来初始化document各个方法的函数setDocument(),接下来主要看一下这个方法都做了什么。
但之前有必要看一下它用到的一些Sizzle内部使用的方法:
// 通过创建一个div元素,检测被传入的fn是否被当前浏览器支持
function assert( fn ) {
var div = document.createElement("div");
//此处用try-catch的原因是:被传入的fn很有可能是会报错的。因为fn中用的方法或属性很可能不被当前浏览器所支持。
try {
// 尝试执行被传入的方法,并将结果返回,如果出错则走catch,直接返回false(当前方法不被支持)
return !!fn( div );
} catch (e) {
return false;
} finally {
// 如果被创建的div存在parentNode,则将当前div删除。
// 这里div看上去不可能有parentNode,因为感觉它没有被插入DOM树。但由于被传入的函数fn很有可能会将这个div插入DOM树,所有必须执行此操作。
if ( div.parentNode ) {
div.parentNode.removeChild( div );
}
// 将div的引用置空,方便回收
div = null;
}
}
// 主要功能是根据各个浏览器对原生方法的支持情况对当前浏览器是否支持某方法进行判别
setDocument = Sizzle.setDocument = function( node ) {
// node是入口函数中传入的context。例如:$( "a", b )。 这里的node指的就是b
// 如果传入了node,则拿到node所在的document,如果node没有ownerDocument,说明它本身就是document; 如果没有传入node,则使用preferredDoc(它的值是window.document)
var doc = node ? node.ownerDocument || node : preferredDoc,
// 拿到当前文档的父文档???
parent = doc.defaultView; // ???
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
return document;
} // Set our document
document = doc;
docElem = doc.documentElement; // 判断当前document是不是HTML
documentIsHTML = !isXML( doc );
// 如果存在父文档,并且父文档存在attachEvent方法(说明是IE),并且父文档不是最顶层??奇怪。。
// 因为IE8及IE8-都不支持defaultView,所以此处会过滤掉IE8-( jQuery2.0不再支持IE8及以下版本 )
// IE9+ 在parent被卸载前setDocument。呃。。晕了。。
if ( parent && parent.attachEvent && parent !== parent.top ) {
parent.attachEvent( "onbeforeunload", function() {
setDocument();
});
}
// 判断getAttribute方法是否能够拿到元素的属性,难道有哪些浏览器是不能通过getAttribute拿到元素属性的吗?呃。。
support.attributes = assert(function( div ) {
div.className = "i";
return !div.getAttribute("className");
}); // 判断getEBTN是否只返回元素节点(某些浏览器使用该方法会返回元素节点和注释节点)
support.getElementsByTagName = assert(function( div ) {
div.appendChild( doc.createComment("") );
return !div.getElementsByTagName("*").length;
}); // 判断当前浏览器是否支持gEBCN方法。
support.getElementsByClassName = assert(function( div ) {
// 在assert方法中创建的div中插入一串DOM。
div.innerHTML = "<div class='a'></div><div class='a i'></div>";
// 给第一个子元素(class为a的元素)的class设置为“i”(不明白为什么要做这一步)
div.firstChild.className = "i";
// 调用gEBCN方法,看class为“i”的元素是不是返回两个(不明白为什么非要判断有两个,有一个不行?)
return div.getElementsByClassName("i").length === 2;
}); // 此处会将assert中创建的div插入DOM树。所以assert中才需要将其从DOM中删除
// 作用是判断当前浏览器通过gEBI方法返回的是否是单纯的ID为相应值得元素(某些浏览器使用gEBI方法可能会返回属性name为对应值得元素)
support.getById = assert(function( div ) {
// 将assert中创建的这个div插入DOM树并将它的id设置为expando(一个Sizzle自己生成的以“sizzle”开头的很长的字符串)。目的是不希望DOM中用户有设置的相同的id
docElem.appendChild( div ).id = expando;
// 如果gEBN方法不存在,或者使用gEBN方法取不到上面插入DOM的div,则说明使用gEBI方法只会查找ID为对应值得元素返回。
return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
}); // 如果gEBI确实只会检查ID,而不是连NAME一起检查的话,初始化Expr的find.ID方法和filter.ID方法
if ( support.getById ) {
// 这里的context应该在调用该方法时会传入document吧!
Expr.find["ID"] = function( id, context ) {
// 如果context上有gEBI方法,并且该文档是HTML
if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
// 调用gEBI方法找到有相应ID的元素
var m = context.getElementById( id );
// 某些浏览器会将文档碎片中的带有相应id的元素找到,因此需要看看该元素是否有父节点。(但是只检查父节点就可以排除吗?)
return m && m.parentNode ? [m] : [];
}
};
// 过滤出id为相应ID的元素
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
// 否则的话,说明使用gEBI并不一定只是单纯的拿到ID为对应值的元素
} else {
// 删除掉绑定Expr的find上的ID方法
delete Expr.find["ID"];
// 给Expr的filter绑定ID方法
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
// 通过拿到当前元素的ID
var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
// 判断当前元素的ID值是否和相应id值相等
return node && node.value === attrId;
};
};
}
// 为Expr的find绑定TAG方法,如果gEBTN方法只返回元素节点。
Expr.find["TAG"] = support.getElementsByTagName ?
// 则将该函数赋值给TAG,当调用TAG方法时会使用gEBTN方法返回想要查找的元素
function( tag, context ) {
if ( typeof context.getElementsByTagName !== strundefined ) {
return context.getElementsByTagName( tag );
}
} :
// 否则的话绑定该方法
function( tag, context ) {
var elem,
tmp = [],
i = 0,
// 拿到一个包含注释节点的元素数组
results = context.getElementsByTagName( tag );
// 如果tag是*
if ( tag === "*" ) {
// 通过遍历结果集中的所有元素
while ( (elem = results[i++]) ) {
// 只将这些元素中nodeType为1的推入临时数组中
if ( elem.nodeType === 1 ) {
tmp.push( elem );
}
}
// 返回这个临时数组
return tmp;
}
// 如果tag不是*,则直接返回结果集即可。
return results;
}; // 为Expr的find绑定CLASS方法,如果浏览器支持该方法则为其绑定方法,否则为undefined
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
// 如果浏览器支持该方法,并且该文档是HTML文档
if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
// 直接调用gEBCN方法返回结果
return context.getElementsByClassName( className );
}
}; rbuggyMatches = [];
rbuggyQSA = [];
// 检测浏览器是否支持高级的qSA方法,并同时为support.qsa赋值。完全不了解qSA,该部分先略过。。。。
if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
// 调用assert方法判断qSA的支持情况。具体做这些的意义不清楚。
assert(function( div ) { div.innerHTML = "<select><option selected=''></option></select>"; // 估计是各个浏览器对qSA查找selected和checked元素有差异。
if ( !div.querySelectorAll("[selected]").length ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
if ( !div.querySelectorAll(":checked").length ) {
rbuggyQSA.push(":checked");
}
}); assert(function( div ) { var input = doc.createElement("input");
input.setAttribute( "type", "hidden" );
div.appendChild( input ).setAttribute( "t", "" ); if ( div.querySelectorAll("[t^='']").length ) {
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
} if ( !div.querySelectorAll(":enabled").length ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
div.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
} if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMatchesSelector) )) ) { assert(function( div ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9)
support.disconnectedMatch = matches.call( div, "div" ); // This should fail with an exception
// Gecko does not error, returns false instead
matches.call( div, "[s!='']:x" );
rbuggyMatches.push( "!=", pseudos );
});
} rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); // contains在Sizzle()的作用域中定义,此处为其赋值
// 首先判断当前浏览器是否支持contains或者cDP(compareDocumentPosition)方法
// 原生的contains有个问题是如果a===b,调用contains依然会返回true
contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
// 如果支持这两个方法其中的一个
function( a, b ) {
// 如果a是document,拿到documentElement节点,否则a就是a 。。呃。。
var adown = a.nodeType === 9 ? a.documentElement : a,
// 拿到b的父节点
bup = b && b.parentNode;
// 如果a是b的父节点。
// 如果b存在父节点并且父节点是元素节点的话:如果存在contains方法,调用contains方法;否则就是存在cDP方法,那就调用CDP方法。
// 增加了这些判断,就是为了将原生js中contains方法的a===b返回true的问题解决。
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
// 如果浏览器不支持上面的两个方法,那就自己写简单的。(这个简单的也没啥问题啊?那为啥还用原生的?做那么多判断还。。)
// 这个简单的contains就是拿到b的父节点,判断父节点是否和a相等。如果相等就返回true。
// 否则就那父节点的父节点和a相比较。直到没有父节点位置。如果始终没有和a相等的b的祖先元素。则返回false
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
}; // 检测元素的位置关系
// 看浏览器是否支持cDP方法
sortOrder = docElem.compareDocumentPosition ?
// 如果支持cDP方法,将该方法赋值给sortOrder
function( a, b ) {
// 如果a等于b,说明是完全相同的元素,
if ( a === b ) {
hasDuplicate = true;
// 返回0
return 0;
}
// 如果a、b都支持cDP方法,调用cDP方法得到a和b的关系值
var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
// 如果a和b的关系值存在的话
if ( compare ) {
// 没看懂
if ( compare & 1 ||
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // 如果a是document 或者document包含a
if ( a === doc || contains(preferredDoc, a) ) {
return -1;
}
// 如果b是document 或者document包含b
if ( b === doc || contains(preferredDoc, b) ) {
return 1;
} // 呃。。。。
return sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0;
} return compare & 4 ? -1 : 1;
} //
return a.compareDocumentPosition ? -1 : 1;
} :
// 如果浏览器不支持cDP方法,将该方法赋值给sortOrder
function( a, b ) {
var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [ a ],
bp = [ b ]; // 如果a等于b,说明是相同的元素,返回0
if ( a === b ) {
hasDuplicate = true;
return 0;
// 如果a不存在父节点或者b不存在父节点,说明其中一个是document节点
} else if ( !aup || !bup ) {
// a是document节点,返回-1
return a === doc ? -1 :
// 如果b是document节点,返回1
b === doc ? 1 :
// 如果a存在父节点,返回-1
aup ? -1 :
// 如果b存在父节点,返回1
bup ? 1 :
// ???
sortInput ?
( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
0; // 如果a和b有相同的父元素
} else if ( aup === bup ) {
// 调用sC方法判断兄弟元素的先后顺序(猜测)
return siblingCheck( a, b );
} cur = a;
// 如果a存在父元素,就把父元素赋值给cur,一直到a的最外层祖先元素
while ( (cur = cur.parentNode) ) {
// 并且每一次都把cur加到ap数组的开头,这样就形成一个a的所有祖先元素的数组
ap.unshift( cur );
}
cur = b;
// 对b执行像上一步中a一样的处理,形成一个b的所有祖先元素的数组
while ( (cur = cur.parentNode) ) {
bp.unshift( cur );
} // 从最外层祖先元素开始遍历a和b的祖先元素数组。依次往里,知道找到开始不同的那一组祖先元素,并得到这个数组index
while ( ap[i] === bp[i] ) {
i++;
}
// 如果存在一个这样的i
return i ?
// 判断这两个开始不同的祖先元素的先后关系
siblingCheck( ap[i], bp[i] ) : // 如果a的这个祖先元素时document,则返回-1
ap[i] === preferredDoc ? -1 :
// 如果b的这个祖先元素是document,返回1
bp[i] === preferredDoc ? 1 :
// 否则返回0
0;
};
// 修成正果,返回一个当前浏览器可用的document
return doc;
};
jQuery Sizzle选择器(三)的更多相关文章
- [转]JQuery - Sizzle选择器引擎原理分析
原文: https://segmentfault.com/a/1190000003933990 ---------------------------------------------------- ...
- jQuery Sizzle选择器(二)
自己开始尝试读Sizzle源码. 1.Sizzle同过自执行函数的方式为自己创建了一个独立的作用域,它可以不依赖于jQuery的大环境而独立存在.因此它可以被应用到其它js库中.实现如下:(fun ...
- jQuery Sizzle选择器(一)
1.浏览器对css选择器采取逆向(从右向左)解析的原因: 如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的div,再往下找,如果遇到不匹配 ...
- jQuery笔记---选择器(三)
1.1查找隐藏的tr元素的个数 $(“table tr:hidden”).size() 查找所有可见的tr元素的个数 $(“table tr:not(:hidden)”).size() 一般是不使 ...
- jQuery源码分析系列(三)Sizzle选择器引擎-下
选择函数:select() 看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用. 简化操作同样根据tokenize ...
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理
这一节要分析的东东比较复杂,篇幅会比较大,也不知道我描述后能不能让人看明白.这部分的源码我第一次看的时候也比较吃力,现在重头看一遍,再分析一遍,看能否查缺补漏. 看这一部分的源码需要有一个完整的概念后 ...
- 【三】jquery之选择器
转自:https://www.cnblogs.com/youfeng365/p/5846650.html jquery参考手册:http://jquery.cuishifeng.cn/index.ht ...
- JQuery Sizzle引擎源代码分析
最近在拜读艾伦在慕课网上写的JQuery课程,感觉在国内对JQuery代码分析透彻的人没几个能比得过艾伦.有没有吹牛?是不是我说大话了? 什么是Sizzle引擎? 我们经常使用JQuery的选择器查询 ...
- Sizzle选择器引擎介绍
一.前言 Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中.我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍. ...
随机推荐
- andorid ndk 各种坑啊 记录下
android jni代码回调java的问题 因为多线程原因会导致找不到java类,无法call函数的问题 问题1找不到java类 在JNI_OnLoad的时候 保存下来 JNIEXPORT jint ...
- SOA及分布式
结合领域驱动设计的SOA分布式软件架构 Windows平台分布式架构实践 - 负载均衡(下) 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载 我终于深入参与了一 ...
- Mac mysql 解决中文乱码
Mac mysql 解决中文乱码问题 出现"???"之类的无法识别的乱码 到/etc目录下自己建一个my.cnf文件(需要最高权限,使用sudo su),然后写入内容: [clie ...
- 使用go,基于martini,和websocket开发简易聊天室
一.首先,需要了解一下websocket基本原理:here 二.go语言的websocket实现: 基于go语言的websocket也有不少,比如github.com/gorilla/websocke ...
- Java读写配置文件——Properties类的简要使用笔记
任何编程语言都有自己的读写配置文件的方法和格式,Java也不例外. 在Java编程语言中读写资源文件最重要的类是Properties,功能大致如下: 1. 读写Properties文件 2. 读写XM ...
- PHP数据库备份与恢复
先说下关于数据库备份与恢复的原理: 1.查找所有表->2.查找所有字段->3.查找所有数据->4.生成SQL 备份注意点: 2=>需要列出所有字段名,字段类型等相关信息 3=& ...
- SharePoint 2013 Deploy Master Page And Page Layout
2013年9月27日的一篇随笔,其实也是自己编写的部署文档,由于客户是HK的,所以描述部分是用英文. 涉及到的内容是关于SharePoint 2013如何部署自定义的母版页和布局页. First, L ...
- Java程序员的IntelliJ IDEA使用教程
前言 博主是Java程序员,以前一直都用myeclipse来开发的,说实话感觉myeclipse毫无美感可言,后来经过同事介绍,认识了IDEA,一眼就相中了IDEA黑色的主题风格,自此就抛弃了旧爱my ...
- unity之UGUI屏幕分辨率調整
1.Canvas的屬性配置: 2.Canvas Scaler的屬性配置: 3.根據不同的屏幕比例動態寫改碩放基準: public float standard_width = 800f; //初始宽度 ...
- 线程与COM
场景: C++部分封装为COM,C#来调用.调用可能是在线程中的. 应用程序退出时,C#的部分 自动清理,某些线程可能还没有完全停掉. COM内部的东西也在释放内存. 这个时候,稍有不慎,就会崩溃. ...