WelcomeMy columnView a series of articles.
compile
After talking about sizzle for so long, I always feel so bad. For a selector, we generate tokens and optimize it. The optimization steps include removing headers and generating seed sets. For these seed sets, we know that the final matching result comes from a part of the set. It seems that the next task is also clear: filter the seeds (or call it matching).
In fact, the matching process is very simple. It is to judge DOM elements, and the weak is the generation relationship (>) or adjacent sibling relationship (+). If it is not satisfied, it will end. If it is a descendant relationship (space) or sibling relationship (~), it will make multiple judgments, either find a correct one or end it. However, backtracking still needs to be considered.
such asdiv > div.seq h2 ~ p
, they have been divided into tokens correspondingly. If each seed goes through the process, it is obviously too troublesome. A reasonable method is to generate a closure function corresponding to each judged token for unified search.
Expr.filter
It is used to generate matching functions. It is about as follows:
Expr.filter = {
"ID": function(id){...},
"TAG": function(nodeNameSelector){...},
"CLASS": function(className){...},
"ATTR": function(name, operator, check){...},
"CHILD": function(type, what, argument, first, last){...},
"PSEUDO": function(pseudo, argument){...}
}
Look at two examples, everything is clear:
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
//This returns a function
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
After analyzing tokens, if it is found that its type is ID, its ID is retained, and a check function is returned with the parameter elem, which is used to judge whether the ID of the DOM is consistent with the ID of tokens. The advantage of this approach is that it is compiled once and executed many times.
So, is that so? Let’s look at other examples:
Expr.filter["TAG"] = function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
//Returns a function
function() { return true; } :
//The parameter is elem
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
};
Expr.filter["ATTR"] = function( name, operator, check ) {
//Returns a function
return function( elem ) {
var result = Sizzle.attr( elem, name );
if ( result == null ) {
return operator === "!=";
}
if ( !operator ) {
return true;
}
result += "";
return operator === "=" ? result === check :
operator === "!=" ? result !== check :
operator === "^=" ? check && result.indexOf( check ) === 0 :
operator === "*=" ? check && result.indexOf( check ) > -1 :
operator === "$=" ? check && result.slice( -check.length ) === check :
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
false;
};
},
Last returned result:
-
Input [type = button] attribute type is button;
-
Input [type! = button] attribute type is not equal to button;
-
Input [name ^ = pre] attribute name starts with pre;
-
Input [name * = new] attribute name contains new;
-
Input [name $= ed] attribute name ends with ED;
-
Input [name = ~ = new] attribute name is new separated by spaces;
-
Input [name | = zh] attribute name is either equal to zh or starts with zh followed by a hyphen -.
Therefore, for a token, a closure function is generated. The parameter received by the function is a DOM, which is used to judge whether the DOM element meets the token constraints, such as ID or classname. If multiple tokens (i.e. tokens) are processed in this way, a special function array for judgment will be obtained. In this way, for each element in the child seed, the function array can be used to judge its parent element or brother node one by one, which greatly improves the efficiency, that is, the so-called compilation once and use multiple times.
Compile source code
Paste the compile function code directly. There will bematcherFromTokens
andmatcherFromGroupMatchers
These two functions are also introduced together.
var compile = function(selector, match) {
var i,setMatchers = [],elementMatchers = [],
cached = compilerCache[selector + " "];
//Judge whether there is cache, as if every function will judge
if (!cached) {
if (!match) {
//Judge whether match generates tokens
match = tokenize(selector);
}
i = match.length;
while (i--) {
//Here we give tokens to this function
cached = matcherFromTokens(match[i]);
if (cached[expando]) {
setMatchers.push(cached);
} else {
elementMatchers.push(cached);
}
}
//Put in cache
cached = compilerCache(
selector,
//This function generates the final matcher
matcherFromGroupMatchers(elementMatchers, setMatchers)
);
// Save selector and tokenization
cached.selector = selector;
}
return cached;
};
Compiling the compile function seems simple. Look at matcherfromtokens:
//
function matcherFromTokens(tokens) {
var checkContext,matcher,j,len = tokens.length,leadingRelative = Expr.relative[tokens[0].type],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0,
//Make sure that all elements can be found
//Addcombiner is for expr Relative to judge
/*
Expr.relative = {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
};
*/
matchContext = addCombinator(
function(elem) {
return elem === checkContext;
},implicitRelative,true),
matchAnyContext = addCombinator(
function(elem) {
return indexOf(checkContext, elem) > -1;
},implicitRelative,true),
matchers = [
function(elem, context, xml) {
var ret = !leadingRelative && (xml || context !== outermostContext) || ((checkContext = context).nodeType ? matchContext(elem, context, xml) : matchAnyContext(elem, context, xml));
// Avoid hanging onto element (issue #299)
checkContext = null;
return ret;
}
];
for (; i < len; i++) {
//Process "empty > ~ +"
if (matcher = Expr.relative[tokens[i].type]) {
matchers = [addCombinator(elementMatcher(matchers), matcher)];
} else {
//Process attr child class ID pseudo tag. Here is the filter function
matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches);
// Return special upon seeing a positional matcher
//The pseudo class divides the selector into two parts
if (matcher[expando]) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for (; j < len; j++) {
if (Expr.relative[tokens[j].type]) {
break;
}
}
return setMatcher(
i > 1 && elementMatcher(matchers),
i > 1 && toSelector(
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
tokens
.slice(0, i - 1)
.concat({value: tokens[i - 2].type === " " ? "*" : ""})
).replace(rtrim, "$1"),
matcher,
i < j && matcherFromTokens(tokens.slice(i, j)),
j < len && matcherFromTokens(tokens = tokens.slice(j)),
j < len && toSelector(tokens)
);
}
matchers.push(matcher);
}
}
return elementMatcher(matchers);
}
Addcombiner function is used to generate curry function to solve expr Relative:
function addCombinator(matcher, combinator, base) {
var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++;
return combinator.first ? // Check against closest ancestor/preceding element
function(elem, context, xml) {
while (elem = elem[dir]) {
if (elem.nodeType === 1 || checkNonElements) {
return matcher(elem, context, xml);
}
}
return false;
} : // Check against all ancestor/preceding elements
function(elem, context, xml) {
var oldCache, uniqueCache, outerCache, newCache = [dirruns, doneName];
// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
if (xml) {
while (elem = elem[dir]) {
if (elem.nodeType === 1 || checkNonElements) {
if (matcher(elem, context, xml)) {
return true;
}
}
}
} else {
while (elem = elem[dir]) {
if (elem.nodeType === 1 || checkNonElements) {
outerCache = elem[expando] || (elem[expando] = {});
// Support: IE <9 only
// Defend against cloned attroperties (jQuery gh-1709)
uniqueCache = outerCache[elem.uniqueID] || (outerCache[elem.uniqueID] = {});
if (skip && skip === elem.nodeName.toLowerCase()) {
elem = elem[dir] || elem;
} else if ((oldCache = uniqueCache[key]) && oldCache[0] === dirruns && oldCache[1] === doneName) {
// Assign to newCache so results back-propagate to previous elements
return newCache[2] = oldCache[2];
} else {
// Reuse newcache so results back-propagate to previous elements
uniqueCache[key] = newCache;
// A match means we're done; a fail means we have to keep checking
if (newCache[2] = matcher(elem, context, xml)) {
return true;
}
}
}
}
}
return false;
};
}
Where the elementmatcher function is used to generate a matcher:
function elementMatcher(matchers) {
return matchers.length > 1 ? function(elem, context, xml) {
var i = matchers.length;
while (i--) {
if (!matchers[i](elem, context, xml)) {
return false;
}
}
return true;
} : matchers[0];
}
matcherFromGroupMatchers
As follows:
function matcherFromGroupMatchers(elementMatchers, setMatchers) {
var bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function(seed, context, xml, results, outermost) {
var elem,j,matcher,matchedCount = 0,i = "0",unmatched = seed && [],setMatched = [],
contextBackup = outermostContext,
// We must always have either seed elements or outermost context
elems = seed || byElement && Expr.find["TAG"]("*", outermost),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = dirruns += contextBackup == null ? 1 : Math.random() || 0.1,len = elems.length;
if (outermost) {
outermostContext = context === document || context || outermost;
}
// Add elements passing elementMatchers directly to results
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
for (; i !== len && (elem = elems[i]) != null; i++) {
if (byElement && elem) {
j = 0;
if (!context && elem.ownerDocument !== document) {
setDocument(elem);
xml = !documentIsHTML;
}
while (matcher = elementMatchers[j++]) {
if (matcher(elem, context || document, xml)) {
results.push(elem);
break;
}
}
if (outermost) {
dirruns = dirrunsUnique;
}
}
// Track unmatched elements for set filters
if (bySet) {
// They will have gone through all possible matchers
if (elem = !matcher && elem) {
matchedCount--;
}
// Lengthen the array for every element, matched or not
if (seed) {
unmatched.push(elem);
}
}
}
// `i` is now the count of elements visited above, and adding it to `matchedCount`
// makes the latter nonnegative.
matchedCount += i;
// Apply set filters to unmatched elements
// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
// no element matchers and no seed.
// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
// case, which will result in a "00" `matchedCount` that differs from `i` but is also
// numerically zero.
if (bySet && i !== matchedCount) {
j = 0;
while (matcher = setMatchers[j++]) {
matcher(unmatched, setMatched, context, xml);
}
if (seed) {
// Reintegrate element matches to eliminate the need for sorting
if (matchedCount > 0) {
while (i--) {
if (!(unmatched[i] || setMatched[i])) {
setMatched[i] = pop.call(results);
}
}
}
// Discard index placeholder values to get only actual matches
setMatched = condense(setMatched);
}
// Add matches to results
push.apply(results, setMatched);
// Seedless set matches succeeding multiple successful matchers stipulate sorting
if (outermost && !seed && setMatched.length > 0 && matchedCount + setMatchers.length > 1) {
Sizzle.uniqueSort(results);
}
}
// Override manipulation of globals by nested matchers
if (outermost) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
}
return unmatched;
};
return bySet ? markFunction(superMatcher) : superMatcher;
}
This process is too complicated. Please forgive me for not being able to read it patiently…
Leave your name first and analyze later…
At this point, in fact, it can be over, but with a responsible attitude, let’s take care of the whole process of sizzle.
Although sizzle is an independent project, it is represented in jQueryjQuery.find
Function, these two functions are actually the same and completely equivalent. Then it introduces the tokensize function, which is called lexical analysis. Its function is to divide the selector into an array of tokens. Each element of the array has value and type values. Then there is the select function, which plays an optimization role, removing the head and tail, and expr The find function generates the seed array.
The following introduction is careless. I read a lot and understand it very well. The compile function performs precompiling, which is to generate a closure function for the remaining selectors after removing seed, and generate a large supermatcher function from the closure function. This function can be used at this timesuperMatcher(seed)
To process seed and get the final result.
So what is supermatcher?
superMatcher
As already said, this iscompile()()
Function, andcompile()
The return value of is supermatcher. Whether introducing matcherfromtokens or matcherfromgroupmatchers, the result is to generate super matches and then process seed. This is a test moment. Only if it can withstand the screening will it stay.
summary
The following is a flowchart summarized by others:
First step
div > p + div.aaron input[type="checkbox"]
Go through expr first from the far right Find obtains the seed array, where the input is tag, sogetElementsByTagName()
Function.
Step 2
Reorganize the selector. At this time, remove the selector after input:
div > p + div.aaron [type="checkbox"]
Step 3
At this time, through expr Relative divides tokens into close relationships and non close relationships according to the relationship. For example, [“>”, “+”] is a close relationship, and its first = true. For [“”, “~”] is a non close relationship. Tight relationships can be quickly judged during screening.
Matcherfromtokens compiles closure functions according to the relationship, which are divided into four groups:
div >
p +
div.aaron
input[type="checkbox"]
The compilation function mainly uses expr Filter and expr relative。
Step 4
Put all the compiled closure functions together to generate the supermatcher function.
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
}
From right to left, process the seed collection. If there is a mismatch, it returns false. If the match is successful, it indicates that the seed element meets the filter conditions and is returned to results.
reference resources
JQuery 2.0.3 source code analysis sizzle engine – compilation function (large space)
JQuery 2.0.3 source code analysis sizzle engine – Super matching
This paper is based on GitHubSource addressWelcome to star.
WelcomeMy blogcommunication.