preface
In my last article, I wrote about the regular expression for the Sizzle, and I’m going to read the documentation in the order that the Sizzle runs, so I’m going to start with this setDocument function, This function is used to determine the browser’s native selector support, old browser compatibility, querySelectorAll bug detection in each browser, and to initialize the sorting function. This function is executed immediately when the Sizzle is referenced, and is called every time the Sizzle is used. Understanding this function will refer to some of the other variables of the Sizzle, which I’ll add to this article as appropriate for your convenience.
Global variables and methods used by SetDocument
By global, I mean the scope in the IIFE
variable
preferredDoc = window.document
document
Here,document
It’s a variable, not the document that we knowdocumentIsHTML
Determine if the current document is HTML becauseSizzle
Is to supportXML
thesupport
This one is to determine compatibility with an objectExpr
We’ll talk about this in a minute, but this is also an object, and this object is used for searching and filtering elementshasDuplicate
Determine whether elements are duplicatedsortInput
This is the final set of elements without sortingrnative
Determines if a regular pattern is used by a native methodexpando = 'sizzle' + 1 * new Date()
Used as a unique identifierrunescape
The previous article had re, determining escape characters
methods
// Check whether it is XML
isXML = function(elem) {
var namespace = elem.namespaceURI,
docElem = (elem.ownerDocument || elem).documentElement;
return! rhtml.test(namespace || docElem && docElem.nodeName ||'HTML');
}
Copy the code
// For validation, this method is used very frequently in the Sizzle, and is used for almost all judgments
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; }}Copy the code
// For escape
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)}Copy the code
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;
}
Copy the code
SetDocument method
The setDocument method is executed once in the IIFE, and has no arguments, for initialization. The method is divided into several parts. 2. Set Expr according to compatibility, 3. QuerySelectorAll compatibility problem, 4. Set sort.
Parameter and iframe compatibility and performance optimization
/ * @ param Element {| Object} an Element or document Element is used to set the document variable * /
setDocument = Sizzle.setDocument = function(node) {
var hasCompare, subWindow,
doc = node ? node.ownerDocument || node : preferredDoc;
// If the element is not a document element, or there is no HTML, or the element is under the same document as the last call
// Let's go straight back and optimize the performance
if (doc == document|| doc.nodeType ! = =9| |! doc.documentElement) {return document;
}
// Update the external global variable
document = doc;
docElem = document.documentElement; documentIsHTML = ! isXML(document);
// This is iframe compatible
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); }}}Copy the code
This part is about updating global variables, performance optimization, and iframe compatibility.
Support Compatibility with native apis of the browser
// IE/Edge and older browsers do not support: Scope pseudo-class
// Check whether the selected element is present (length should be 0 because there is no pseudo-class :scope added)
// If length is not equal to 0, then not supported; If it's 0, it's support
support.scope = assert( function( el ) {
docElem.appendChild( el ).appendChild( document.createElement('div'));return typeofel.querySelectorAll ! = ='undefined' &&
!el.querySelectorAll(':scope fieldset div').length
} );
// Check whether attributes and attributes overlap
// After assigning a value to an element's attribute, IE can get the value by getting the same attribute name
support.attributes = assert( function( el ) {
el.className = 'i';
return! el.getAttribute('className'); });// Check whether getElementsByTagName returns only element nodes
support.getElementsByTagName = assert( function( el ) {
el.appendChild(document.createComment(' '));
return! el.getElementsByTagName(The '*').length
} );
// Determine whether getElementsByClassName is a native method for browsing
support.getElementsByClassName = rnative.test(document.getElementsByClassName);
// Check if getElementById returns the element by name
// However, older versions of the getElementById method cannot get the id set programmatically
// So use getElementsByName to see if you can get the ID through this method
support.getById = assert( function( el ) {
docElem.appendChild(el).id = expando;
return !document.getElementsByName || !document.getElementsByName(expando).length; })Copy the code
Set the Expr
Here is to set Expr according to the support situation just now. About Expr, I think I will write a separate article about it. Although I haven’t read it, I feel that it should be very important, so I don’t need to write it slowly.
//ID
if (support.getById) {
Expr.filter["ID"] = function(id) {
/ / code
var attrId = id.replace(runescape, funescape);
return function(elem) {
return elem.getAttribute('id') == attrId; }}; Expr.find["ID"] = function(id, context) {
if (typeofcontext.getElementById ! = ='undefined' && documentIsHTML) {
var elem = context.getElementById(id);
return elem ? [elem] : []
}
}
} else {
Expr.filter["ID"] = function(id) {
/ / code
var attrId = id.repalce(runescape, funescape);
return function(elem) {
var node = typeofelem.getAttributeNode ! = ='undefined' &&
elem.getAttributeNode('id');
returnnode && node.value === attrId; }};// In IE 6-7, getElementById is not a reliable lookup method
Expr.find["ID"] = function(id, context) {
if (typeofcontext.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[]; }}}Copy the code
GetElementById in older versions of IE, namely IE6 and 7, returns the first element whose name or ID matches the value. Therefore, after obtaining the element, you can correctly determine the element by obtaining the id attribute of the element and judging whether it is equal to the input value. But there is also the old IE getAttribute problem, which can be seen in this blog post by Stu.
//Tag
Expr.find['TAG'] = support.getElementsByTagName ?
function(tag, context) {
if (typeofcontext.getElementsByTagName ! = ='undefined') {
return context.getElementsByTagName(tag);
// The DocumentFragment node has no getElementsByTagName method
} else if (support.qsa) {
returncontext.querySelectorAll(tag); }} :function(tag, context) {
var elem,
tmp = [],
i = 0,
results = context.getElementsByTagName(tag);
// Filter out nodes that are not elements
if (tag === The '*') {
while ( ( elem = results[i++] ) ) {
if (elem.nodeType === 1) { tmp.push(elem); }}return tmp;
}
return results;
}
Copy the code
GetElementsByTag is an easy one. If you select all, you filter out the non-elements.
//Class
Expr.find['CLASS'] = support.getElementsByClassName && function(className, context) {
if (typeofcontext.getElementsByClassName ! = ='undefined' && documentIsHTML) {
returncontext.getElementsByClassName(className); }}Copy the code
GetElementsByClassName is something you use, something you don’t.
QuerySelectorAll compatibility problem
We first determine whether the browser supports the querySelectorAll and matches methods. If so, then determine compatibility issues. This part is difficult to read, because many of the compatibilities are not encountered, almost according to the comments, these compatibilities can not be too careful, the key is to understand the idea, such as intentionally looking for an error to catch the external function instead of executing the following code. I thought it was really cool when I first saw it.
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 does not handle the Boolean and 'value' attributes properly
if(! el.querySelectorAll("[selected]").length) {
rbuggyQSA.push("\ \ [" + whitespace + "* (? :value|" + booleans + ")");
}
//\r is a space so it should match
if(! el.querySelectorAll("[id~=" + expando + "-]").length) {
rbuggyQSA.push('~ =');
}
input = document.createElement("input");
input.setAttribute("name"."");
el.appendChild(input);
// In some cases, IE11 and Edge cannot find the element [name=""]
// But the interesting thing is that the old IE has no problem
if(! el.querySelectorAll("[name='']").length) {
rbuggQSA.push("\ \ [" + whitespace + "*name" + whitespace + "* =" + whitespace + "* (? : '| \ "\")");
}
//IE8 will report an error here, and the following code will not be executed by the assert method catch
// Select the selected option
if(! el.querySelectorAll(":checked").length) {
rbuggyQSA.push(":checked");
}
//Safari 8+, IOS 8+
//ID + sibling selector is invalid
if(! el.querySelectorAll("a#" + expando + "+ *").length) {
rbuggyQSA.push(". #. + [+ ~]. "");
}
// Only old Firefox does not report errors when using incorrect escape characters
// If there is an error, it will go straight to the catch of assert, and it will not run the last 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");
// The name attribute is case sensitive
if (el.querySelectorAll("[name=d]").length) {
rbuggyQSA.push("name" + whitespace + "* / * ^ $|! ~? =");
}
// Option and input should be selected properly
// IE8 will not execute the test below
if (el.querySelectorALL(':enabled').length ! = =2) {
rbuggyQSA.push(":enabled".":disabled");
}
// IE9-11 :disabled Does not select a subset of disabled fieldSet elements
docElem.appendChild(el).disabled = true;
if (el.querySelectorAll(':disabled').length ! = =2) {
rbuggyQSA.push(':enabled'.':disabled');
}
// Opera 10-11 does not report an invalid pseudo-class;
// This rule is not added by catch if an error is reported
// If there is no error, add it
el.querSelectorAll('*,:x');
rbuggyQSA.push('. * :); }); }if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMactesSelector) ) ) ) {
assert(function(el) {
// Matches can be used to detect elements that are not in the DOM tree
support.disconnectedMatch = matches.call(el, "*");
// Gecko only returns false when it should
// Catch the error
matches.call(el, '[s!= ""]:x');
rbuggyMatches.push('! = ', pseudos ); }); }// Convert to regular expressions or relationships
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|")); rbuggyMatches = rbuggyMatches.length &&new RegExp( rbuggyMatches.join( "|"));Copy the code
The sorting
The idea behind the Sizzle sorting is to use the native array.prototype. sort method, so sorting is basically a callback function to sort. We set up the rules and let the browser sort for us. The main thing that’s used for sorting is the compareDocumentPosition method, and the Sizzle is going to tell if there’s a method, and if there isn’t, then it’s going to iterate.
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;
returna === 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;
}
// If only one has compareDocumentPosition
// Who has who in front
varcompare = ! a.compareDocumentPosition - ! b.compareDocumentPostion;if (compare) {
return compare;
}
// Check to see if one is under one document
// If not, there is no relationship between the two nodes
compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
a.compareDocumentPosition(b) :
1;
Detached /** suport.sortdetached = assert(function(el) {this should return 1 but may return 4 el.compareDocumentPostion(document.createElement('fieldset')) & 1; }) * * /
// If there are elements that are not in the DOM tree
// Who is in front of the DOM
// If both of them are not in place, arrange them in their original position
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];
// If there is no parent, then either document or not in the DOM tree
if(! aup || ! bup) {return a == document ? - 1 : // If a is a document, a comes first
b == document ? 1 : // If b is a document, b comes first
aup ? - 1 : // If a is in the DOM tree, a is in front
bup ? 1 : // If b is in the DOM tree, b is in front
sortInput ? // If both of them are not in the same order
(indexOf(sortInput, a) - indexOf(sortInput, b)) :
0;
} else if (aup === bup) {
// If the parent is the same, then nextSibling can be directly determined
return siblingCheck(a, b);
}
// If not, start from scratch
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 = nextSibling = nextSibling = nextSibling = nextSibling = nextSibling
siblingCheck(ap[i], bp[i]) :
ap[i] === perferredDoc ? - 1 :
bp[i] === preferredDoc ? 1 :
0
}
Copy the code
SetDocument full version
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 typeofel.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(The '*').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 (typeofcontext.getElementById ! = ='undeifined' && documentIsHTML) {
var elem = context.getElementById( id );
returnelem ? [elem] : []; }}}else {
Expr.filter["ID"] = function(id) {
var attrId = id.replace(runescape, funescape);
return function(elem) {
var node = typeofelem.getAttributeNode ! = ='undefined' &&
elem.getAttributrNode('id');
returnnode && node.value === attrId; }}; Expr.find["ID"] = function(id, context) {
if (typeofcontext.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) {
returnelem; }}}return[]; }}}//Tag
Expr.find["TAG"] = support.getElementsByTagName ?
function(tag, context) {
if (typeofcontext.getElementsByTagName ! = ='undefined') {
return context.getElementsByTagName(tag);
} else if (support.qsa) {
returncontext.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 (typeofcontext.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;
returna === 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;
}
varcompare = ! 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;
}
Copy the code