上一篇文章写的关于
Sizzle
的正则表达式,我打算就按照Sizzle
的运行顺序进行阅读文档,因此身先士卒就是这个setDocument
函数了,这个函数主要作了浏览器的原生选择器支持状况判断、老浏览器的兼容、querySelectorAll
在个浏览器的bug排查和排序函数的初始化。函数会在Sizzle
被引用的时候当即执行一次,而且在每次使用Sizzle
的时候都会调用,读懂这个函数会涉及Sizzle
一些其余的变量,这里我会酌情加到这篇文章里面,方便你们阅读。javascript
这里的全局指的是IIFE中的做用域 html
preferredDoc = window.document
document
这里的document
是个变量不是咱们所熟知的那个documentdocumentIsHTML
判断当前文档是否是HTML 由于Sizzle
是支持XML
的support
这个是判断兼容用的是一个对象Expr
这个之后会单说,这个也是一个对象,这个对象是作搜索以及过滤元素用的hasDuplicate
判断元素是否有重复sortInput
这个就是没有进行排序的最终元素集rnative
判断是否是原生方法用的正则expando = 'sizzle' + 1 * new Date()
做为惟一标识使用runescape
上一篇文章有, 正则, 判断转义字符//判断是否为XML
isXML = function(elem) {
var namespace = elem.namespaceURI,
docElem = (elem.ownerDocument || elem).documentElement;
return !rhtml.test(namespace || docElem && docElem.nodeName || 'HTML');
}
复制代码
//用于验证,这个方法在Sizzle中使用很是频繁, 几乎全部的判断兼用都用了这个方法
function assert(fn) {
var el = document.createElement("fieldset");
try {
return !!fn(el);
} catch(e) {
return false;
} finally {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
el = null;
}
}
复制代码
//转义用的
funescape = function(escape, nonHex) {
var high = '0x' + escape.slice( 1 ) - 0x10000;
return noHex ?
noHex:
high < 0 ?
String.fromCharCode(high + 0x10000) :
String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00)
}
复制代码
function siblingCheck(a, b) {
var cur = b && a,
diff = cur && a.nodeType === 1 && b.nodeType == 1 &&
a.sourceIndex - b.sourceIndex;
if (diff) {
return diff
}
if (cur) {
while((cur = cur.nextSibling)) {
if (cur === b) {
return -1
}
}
}
return a ? 1 : -1;
}
复制代码
setDocument方法会在IIFE
中执行一次,而且没有参数,用来初始化。
方法分为几个部分。1.原生搜索方法兼容,2.根据兼容设置Expr,3.querySelectorAll兼容问题,4.设置排序。java
/* @param {Element|Object} 一个元素或者文档元素用来设置document变量 */
setDocument = Sizzle.setDocument = function(node) {
var hasCompare, subWindow,
doc = node ? node.ownerDocument || node : preferredDoc;
// 这里若是不是document元素或者没有html,再或者和上一次调用的时候元素处在同一个document下面
//那就直接返回, 性能优化
if (doc == document || doc.nodeType !== 9 || !doc.documentElement) {
return document;
}
//更新外部的全局变量
document = doc;
docElem = document.documentElement;
documentIsHTML = !isXML(document);
//这块就是兼容iframe的
if ( perferredDoc != document &&
(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
if ( subWindow.addEventListener ) {
subWindow.addEventListener('unload', unloadHandler, false);
} else if ( subWindow.attachEvent ) {
subWindow.attachEvent('onunload', unloadHandler);
}
}
}
复制代码
这部分就是作了更新全局变量、性能优化、以及iframe的兼容。node
// IE/Edge和老浏览器不支持:scope这个伪类
// 判断会不会有选中的元素(由于没有添加:scope这个伪类, 因此length应该为0)
// 若是length不等于0,那么不支持; 若是为0,就是支持
support.scope = assert( function( el ) {
docElem.appendChild( el ).appendChild( document.createElement('div') );
return typeof el.querySelectorAll !== 'undefined' &&
!el.querySelectorAll(':scope fieldset div').length
} );
//判断属性与特性是否重叠
//老IE在给元素的特性赋值后, 能够经过获取相同的属性名获取到该值
support.attributes = assert( function( el ) {
el.className = 'i';
return !el.getAttribute('className');
} );
//检查getElementsByTagName是否只返回元素节点
support.getElementsByTagName = assert( function( el ) {
el.appendChild(document.createComment(''));
return !el.getElementsByTagName('*').length
} );
//判断getElementsByClassName是否为浏览其原生方法
support.getElementsByClassName = rnative.test(document.getElementsByClassName);
// 检查若是getElementById是不是经过name返回元素的
// 可是老版本的getElementById方法不能拿到编程方式设置的id
// 因此使用getElementsByName看看经过这个方法时候能获取id
support.getById = assert( function( el ) {
docElem.appendChild(el).id = expando;
return !document.getElementsByName || !document.getElementsByName(expando).length;
} )
复制代码
这里就是根据刚刚的support状况去设置Expr。关于Expr我估计我会单独用一篇文章去写它,虽然没看过它,可是感受这兄弟应该很重要,不急慢慢写。web
//ID
if (support.getById) {
Expr.filter["ID"] = function(id) {
//转码
var attrId = id.replace(runescape, funescape);
return function(elem) {
return elem.getAttribute('id') == attrId;
}
};
Expr.find["ID"] = function(id, context) {
if (typeof context.getElementById !== 'undefined' && documentIsHTML) {
var elem = context.getElementById(id);
return elem ? [elem] : []
}
}
} else {
Expr.filter["ID"] = function(id) {
//转码
var attrId = id.repalce(runescape, funescape);
return function(elem) {
var node = typeof elem.getAttributeNode !== 'undefined' &&
elem.getAttributeNode('id');
return node && node.value === attrId;
}
};
//IE 6 -7 的时候getElementById不是一个可靠的查找方法
Expr.find["ID"] = function(id, context) {
if (typeof context.getElemntById !== 'undefined' && documentIsHTML) {
var node, i, elems,
elem = context.getElementById(id);
if (elem) {
node = elem.getAttributeNode('id');
if (node && node.value === id) {
return [elem];
}
elems = context.getElementsByName(id);
i = 0;
while(elem = elems[i++]) {
node = elem.getAttributeNode('id');
if (node && node.value === id) {
return [elem]
}
}
}
return [];
}
}
}
复制代码
getElementById
在老IE版本中也就是IE六、7中,会返回第一个name或者id为匹配值的元素,因此在获取到元素后,经过获取元素的id
属性并判断是否等于输入的值,才能正确判断元素。可是还有一个问题是就是老的IE的getAttribute
的问题,具体能够看司徒大大写的这篇博客。正则表达式
//Tag
Expr.find['TAG'] = support.getElementsByTagName ?
function(tag, context) {
if (typeof context.getElementsByTagName !== 'undefined') {
return context.getElementsByTagName(tag);
//DocumentFragment节点是没有getElementsByTagName方法的
} else if (support.qsa) {
return context.querySelectorAll(tag);
}
} :
function(tag, context) {
var elem,
tmp = [],
i = 0,
results = context.getElementsByTagName(tag);
//把不是元素的Node节点过滤掉
if (tag === '*') {
while ( ( elem = results[i++] ) ) {
if (elem.nodeType === 1) {
tmp.push(elem);
}
}
return tmp;
}
return results;
}
复制代码
getElementsByTag
这个就比较简单了,若是是全选话,把不是元素的节点过滤掉就行了。编程
//Class
Expr.find['CLASS'] = support.getElementsByClassName && function(className, context) {
if (typeof context.getElementsByClassName !== 'undefined' && documentIsHTML) {
return context.getElementsByClassName(className);
}
}
复制代码
getElementsByClassName
就是有就用,没有就不用了。浏览器
这里先判断了浏览器支不支持querySelectorAll
和matches
这两个方法。若是支持的话,再去判断兼容问题。这部分看的时候比较难懂,由于不少兼容都没有碰到过,几乎都是按照注释来看的,这些兼容能够不用太留心,关键是理解一下思路,好比故意找报错让外部的函数catch
而再也不执行下面的代码。我第一次看的时候以为很是的酷。ruby
rbuggyMatches = [];
rbuggyQSA = [];
if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) {
assert( function (el) {
var input;
docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
"<option selected=''></option></select>";
//^= $= *=后面接空字符串时, 应该什么都不选中
if (el.querySelectorAll("[msallowcapture^='']").length) {
rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
}
//IE8 仍是布尔属性和'value'属性不能正常处理
if (!el.querySelectorAll("[selected]").length) {
rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
}
//\r是空格因此应该能够匹配的到
if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
rbuggyQSA.push('~=');
}
input = document.createElement("input");
input.setAttribute("name", "");
el.appendChild(input);
//IE11和Edge在某些状况下找不到[name=""]的元素
//可是有意思的是 老的IE却没有问题
if (!el.querySelectorAll("[name='']").length) {
rbuggQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")");
}
//IE8在这里会报错, 而后被assert方法catch下面的代码不会再执行
//这里是能够选择select中被selected的option的
if (!el.querySelectorAll(":checked").length) {
rbuggyQSA.push(":checked");
}
//Safari 8+, IOS 8+
//ID + 兄弟选择器会失效
if (!el.querySelectorAll("a#" + expando + "+*").length) {
rbuggyQSA.push(".#.+[+~]");
}
//当用的是错误的转义字符的话, 只有老火狐不会报错
//若是报错, 仍是会直接走assert的catch了, 就不会运行最后这个push了, 这个写法很是的酷;
el.querySelectorAll('\\\f');
rbuggyQSA.push("[\\r\\n\\f]");
} );
assert(function (el) {
el.innerHTML = "<a href='' disabled='disabled'></a>" +
"<select disabled='disabled'><option/></select>";
var input = document.createElement("input");
input.setAttribute("type", "hidden");
el.appendChild( input ).setAttribute("name", "D");
// name属性区分大小写的问题
if (el.querySelectorAll("[name=d]").length) {
rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
}
// option 和 input 应该被正常选中
// IE8在这里会报错 下面的test都再也不执行
if (el.querySelectorALL(':enabled').length !== 2) {
rbuggyQSA.push(":enabled", ":disabled");
}
// IE9-11 :disabled不选择disabled的fieldset元素的子集
docElem.appendChild(el).disabled = true;
if (el.querySelectorAll(':disabled').length !== 2) {
rbuggyQSA.push(':enabled', ':disabled');
}
// Opera 10-11 逗号后伪类无效是不会报错的;
// 这里仍是 若是报错了 就直接被catch不添加这个规则
// 若是没报错就加进去
el.querSelectorAll('*,:x');
rbuggyQSA.push(',.*:');
} );
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMactesSelector) ) ) ) {
assert(function(el) {
// 不在DOM树上的元素, 是否能够用matches检测
support.disconnectedMatch = matches.call(el, "*");
// 这里本应该报错的, 可是Gecko只返回false
// 这里也是,若是报错直接catch
matches.call(el, '[s!= ""]:x');
rbuggyMatches.push('!=', pseudos );
} );
}
// 转成正则表达式 或关系
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
复制代码
Sizzle的排序思路是使用了原生的Array.prototype.sort
方法,因此所谓排序其实就是配置sort
的回调函数,咱们设置好规则,让浏览器来帮咱们进行排序。主要排序用的就是compareDocumentPosition
这个方法,Sizzle
会先判断有没有这个方法, 若是没有那就只能遍历了。性能优化
hasCompare = rnative.test(docElem.compareDocumentPosition);
contains = hasCompare || rnative.test(docELem.contains) ?
function (a, b) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!(bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16
) )
} :
function (a, b) {
if (b) {
while ((b = b.parentNode)) {
if (a === b) {
return true;
}
}
}
return false;
}
sortOrder = hasCompare ?
function(a, b) {
if (a === b) {
hasDuplicate = true;
return 0;
}
//若是只有一个有compareDocumentPosition
//谁有谁排前面
var compare = !a.compareDocumentPosition - !b.compareDocumentPostion;
if (compare) {
return compare;
}
//查看一下是否是一个在一个document下
//若是不是的话 那么两个节点之间不存在关系
compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
a.compareDocumentPosition(b) :
1;
/** suport.sortDetached = assert(function( el ) { 这里应该返回1 可是有可能返回4 return el.compareDocumentPostion(document.createElement('fieldset')) & 1; }) **/
// 若是这里是有不在DOM树中的元素
// 谁在DOM中谁在前面
// 若是两个都不在 那就按本来的位置排列
if (compare & 1 ||
(!suport.sortDetached && compare === b.compareDocumentPosition(a))
) {
if (a == document || a.ownerDocument == preferredDoc && contains(preferredDoc, a)) {
return -1;
}
if (b == document || b.ownerDocument == preferredDoc &&
contains(preferredDoc, b)) {
return 1;
}
return sortInput ?
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0;
}
return compare & 4 ? -1 : 1;
} :
function (a, b) {
if (a === b) {
hasDuplicate = true;
return 0;
}
var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [a],
bp = [b];
// 若是没有父节点 那么不是document就是不在DOM树上
if (!aup || !bup) {
return a == document ? -1 : //若是a是document, a在前面
b == document ? 1 : //若是b是document, b在前面
aup ? -1 : //若是a在DOM树上, a在前面
bup ? 1 : //若是b在DOM树上, b在前面
sortInput ? //若是都不在 那就正常排序把
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0;
} else if (aup === bup) {
// 若是父级相同 那么能够经过nextSibling直接判断
return siblingCheck(a, b);
}
// 若是都不是的话 那就从头开始比
cur = a;
while( (cur = cur.parentNode) ) {
ap.unshift(cur);
}
cur = b;
while( (cur = cur.parentNode) ) {
bp.unshift(cur);
}
while(ap[i] === bp[i]) {
i++;
};
return i ?
//找到了不一样的父集节点 仍是继续经过nextSibling判断谁在前面
siblingCheck(ap[i], bp[i]) :
ap[i] === perferredDoc ? -1 :
bp[i] === preferredDoc ? 1 :
0
}
复制代码
setDocument = Sizzle.setDocument = function(node) {
var hasCompare, subWindow,
doc = node ? node.ownerDocument || node : preferredDoc;
if (doc == document || doc.nodeType !== 9 || !doc.documentElement) {
return document;
}
document = doc;
docElem = document.documentElement;
documentIsHTML = !isXML(document);
if (perferredDoc != document &&
(subWIndow = document.defaultView) && subWindow.top !== subWindow) {
if (subWIndow.addEventListener) {
subWindow.addEventListener('unload', unloadnHandler, false);
} else if (subWindow.attachEvent) {
subWindow.attachEvent('onunload', unloadHandler);
}
}
support.scope = assert( function(el) {
docElme.appednChild(el).appendChild(document.createElement('div'));
return typeof el.querySelectorAll !== 'undefined' &&
!el.querySelector(':scope fieldset div').length;
} );
support.attributes = assert(function(el) {
el.className = 'i';
return !el.getAttribute('className');
});
support.getElementsByTagName = assert( function(el) {
el.appendChild(document.createComment"");
return !el.getElementsByTagName('*').length;
} );
support.getElementsByClassName = rnative.test(document.getElementsByClassName);
support.getById = assert( function(el) {
docElem.appendChild(el).id = expando;
return !document.getElementsByName || !document.getElementsByName(expando).length;
} );
//ID
if (support.getById) {
Expr.filter["ID"] = function(id) {
var attrId = id.replace(runescape, funescape);
return function(elem) {
return elem.getAttribute('id') === attrId;
}
}
Expr.find["ID"] = function(id, context) {
if (typeof context.getElementById !== 'undeifined' && documentIsHTML) {
var elem = context.getElementById( id );
return elem ? [elem] : [];
}
}
} else {
Expr.filter["ID"] = function(id) {
var attrId = id.replace(runescape, funescape);
return function(elem) {
var node = typeof elem.getAttributeNode !== 'undefined' &&
elem.getAttributrNode('id');
return node && node.value === attrId;
}
};
Expr.find["ID"] = function(id, context) {
if (typeof context.getElementById !== 'undefined' && documentIsHTML) {
var node, i, elems,
elem = context.getElemetById(id);
if (elem) {
node = elem.getAttributeNode("id");
if (node && node.value === attrId) {
return [elem];
}
elems = document.getElementsByName(id);
i = 0;
while( (elem = elems[i++]) ) {
node = elem.getAttributeNode("id");
if (node && node.value === attrId) {
return elem;
}
}
}
return [];
}
}
}
//Tag
Expr.find["TAG"] = support.getElementsByTagName ?
function(tag, context) {
if (typeof context.getElementsByTagName !== 'undefined') {
return context.getElementsByTagName(tag);
} else if (support.qsa) {
return context.querySelectorAll(tag);
}
} :
function(tag, context) {
var elem,
tmp = []
i = 0,
results = context.getElementsByTagName(tag);
if (tag === "*") {
while ( (elem = results[i++]) ) {
if (elem.nodeType === 1) {
tmp.push(elem);
}
}
return tmp;
}
return results;
}
//Class
Expr.find["CLASS"] = support.getElementsByClassName && function(className, context) {
if (typeof context.getElementsByClassName !== 'undefined' && documentIsHTML) {
return context.getElementsByClassName(className);
}
}
rbuggyMatches = [];
rbuggyQSA = [];
if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) {
assert( function (el) {
var input;
docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
"<option selected=''></option></select>";
if (el.querySelectorAll("[msallowcapture^='']").length) {
rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")");
}
if (!el.querySelectorAll("[selected]").length) {
rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
}
if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
rbuggyQSA.push('~=');
}
input = document.createElement("input");
input.setAttribute("name", "");
el.appendChild(input);
if (!el.querySelectorAll("[name='']").length) {
rbuggQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")");
}
if (!el.querySelectorAll(":checked").length) {
rbuggyQSA.push(":checked");
}
if (!el.querySelectorAll("a#" + expando + "+*").length) {
rbuggyQSA.push(".#.+[+~]");
}
el.querySelectorAll('\\\f');
rbuggyQSA.push("[\\r\\n\\f]");
} );
assert(function (el) {
el.innerHTML = "<a href='' disabled='disabled'></a>" +
"<select disabled='disabled'><option/></select>";
var input = document.createElement("input");
input.setAttribute("type", "hidden");
el.appendChild( input ).setAttribute("name", "D");
if (el.querySelectorAll("[name=d]").length) {
rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
}
if (el.querySelectorALL(':enabled').length !== 2) {
rbuggyQSA.push(":enabled", ":disabled");
}
docElem.appendChild(el).disabled = true;
if (el.querySelectorAll(':disabled').length !== 2) {
rbuggyQSA.push(':enabled', ':disabled');
}
el.querSelectorAll('*,:x');
rbuggyQSA.push(',.*:');
} );
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMactesSelector) ) ) ) {
assert(function(el) {
support.disconnectedMatch = matches.call(el, "*");
matches.call(el, '[s!= ""]:x');
rbuggyMatches.push('!=', pseudos );
} );
}
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );
//Contains
hasCompare = rnative.test(docElem.compareDocumentPostion);
contains = hasCompare || rnative.test(docElem.contains) ?
function(a, b) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ?
adown.contains(bup) :
a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16;
) );
} :
function (a, b) {
if (b) {
while ( (b = b.parentNode) ) {
if (a === b) {
return true;
}
}
}
return false;
}
//Sorting
sortOrder = hasCompare ?
function(a, b) {
if (a === b) {
hasDuplicate = true;
return 0;
}
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
if (compare) {
return compare;
}
compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
a.compareDocumentPosition(b) :
1
if (compare & 1 ||
( !support.sortDetached && b.compareDocumentPosition(a) === compare )) {
if (a == document || a.ownerDocument == preferredDoc &&
contains(preferredDoc, a)) {
return -1;
}
if (b == document || b.ownerDocument == preferredDoc &&
contains(preferredDoc, b)) {
return 1;
}
return sortInput ?
(indexOf(sortInput, a) - indexOf(sortInput,b)) :
0;
}
return compare & 4 ? -1 : 1;
} :
function(a, b) {
if (a === b) {
hasDuplicate = true;
return 0;
}
var cur,
i = 0,
aup = b.parentNode,
bup = b.parentNode,
ap = [ a ],
bp = [ b ];
if (!aup || !bup) {
return a == document ? -1 :
b == document ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0;
} else if (aup === bup) {
return siblingCheck(a, b);
}
cur = a;
while( (cur = cur.parentNode) ) {
ap.unshift(cur);
}
cur = b;
while( (cur = cur.parentNode) ) {
bp.unshif(cur);
}
while (ap[i] === bp[i]) {
i++
}
return i ?
siblingCheck(ap[i] : bp[i]) :
ap[i] = preferredDoc ? -1 :
bp[i] = preferredDoc ? 1 :
0;
}
return document;
}
复制代码