Introduction to the

How does a CSS selector select an element? Sizzle checks an element in CSS from right to left. The reason is efficiency. Please look at the following example. If we want to select.header.nav. nav-item a. So if we go from left to right, the first way to look is to find the.header class in the first div, go to the.nav-item under.nav, and then select the element with the A tag. The second way to find the tag is to go down the node labeled main, to go down the node labeled header, to go down the node labeled.nav, to find the element labeled.nav-item, to find no a tag. So that’s it, so if there’s a lot of similar class values, we’re going to iterate through them one by one. It’s a waste of our query efficiency. But if we look from the right to the left of tag A, and then work our way up, and check to see if it’s the parent of tag A, that saves us a lot of unnecessary lookups. From right to left this query efficiency is not significantly improved?

<! DOCTYPEhtml>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
	<title>Document</title>
</head>
<body>
	<div class="header">
		<div class="nav">
			<div class="nav-item">
				<a href="#"></a>
			</div>
		</div>
	</div>
	<main>
		<div class="header">
			<div class="nav">
				<div class="nav-item">
				</div>
			</div>
		</div>
	</main>
</body>
</html>
Copy the code

How does Sizzle implement these validations?

Hello, students, here I think for a long time to write this blog, because I find that sometimes I write source code interpretation is too empty and soulless, so here to write when I add my personal thinking and my understanding. Hope to really write a good blog, rather than keep a running account. We sizzle to select an HTML element in the same way we select an element in the same way. First, select the rightmost element, then check the parent element level by level, and finally determine whether there is an element or not. I hope you can watch the two examples patiently.

Example 1

So let me do an example like in the HTML below I’m going to select this main nav. nav-item. Can be selected, the answer is definitely not selected. So I’m going to do something like this, and I’m going to select dot nav-item from the right. And then I’m going to go up and check if there’s a parent element whose className is.nav, and if there’s a parent element whose tag is main. Returns the selected. Nav-item if there is one. If not, null is returned. I understand these basic ideas.

<! DOCTYPEhtml>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
	<title>Document</title>
</head>
<body>
	<div class="header">
		<div class="nav">
			<div class="nav-item">
				<a href=""></a>
			</div>
		</div>
	</div>
</body>
</html>
Copy the code

Example 2

Our selector is “.p-box p:nth-child(2n) “, and the sizzle selector will parse it into {type:”CLASS”, value: ‘p-box’}/{type:”TAG”, value: ‘p’}/{type:”CHILD”, value: ‘nth-child(2n)’}, according to the p element tag, then get the pseudo-class selector to filter the element. Four p elements will be selected and returned.

<! DOCTYPEhtml>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
	<title>Document</title>
</head>
<body>
	<div class="p-box">
		<p>I was number one</p>
		<p>I was number two</p>
		<p>I was number three</p>
		<p>I was number four</p>
		<p>I was number five</p>
		<p>I was number six</p>
		<p>I was number seven</p>
		<p>I was number eight</p>
	</div>
</body>
</html>
Copy the code

We’ve got the general idea, and then we start looking at how to select elements, and then filter elements to verify that they are the ones we want to select.

How does JavaScript select an element

As you all know, you can select elements using the tag’s ID attribute, class attribute, or tag name. Previous browsers didn’t support querySelectorAll or had some bugs, We want to realize this querySelectorAll will according to the document. The getElementById, document. The getElementsByClassName, document. The getElementsBytagName combined use to reality So this API right here.

And I mentioned the idea at the beginning that if we use left to right and right to left ways of selecting elements. Suppose we enter a string of selectors “#header >.nav. nav-item”. When a string is passed in, we need to break up the string of selectors and classify them so that it becomes an API that JavaScrip can select and recognize. For example, if we want to split the #header, we know that it is the ID selector, > is the child selector, and.nav is the class selector. Then use the corresponding API to find the element. So what’s the element we’re looking for? It’s going to be nav-item. So let’s find.nav-item and then let’s check it level by level. Ok by this point I believe you should understand. Here’s how to classify and label this list of selectors, commonly known as lexical parsing.

Tokenize selector lexical resolution

The way I understand this lexical parsing is to take our individual selectors and break them up into the smallest particles, add all sorts of identifiers, and JavaScript will figure out what the selectors really are.

Tokenize takes two arguments that identify the selector from left to right. It assembles each selector into a minimum particle containing what type of selector it is, such as #header {type: ‘ID’, value: ‘header’, matches: {0: ‘header’, groups: undefined …. }} in this form. This is a token. If you parse all of the above selectors and put them in, then you have tokens. You have an array to store these tokens. Put it in groups, and finally return the groups out. Now that we’ve parsed all the selectors and returned them, it’s time to start selecting them. That’s the next step. select

/** * [tokenize] * @param {[string]} selector * @param {[boolean]} parseOnly * @return {[array]} */ tokenize = Sizzle.tokenize = function(selector, parseOnly) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[selector + " "]; if (cached) { return parseOnly ? 0 : cached.slice(0); } soFar = selector; groups = []; preFilters = Expr.preFilter; while (soFar) { // Comma and first run if (! Matched | | (match = rcomma. Exec (soFar))) {if (match) {/ / Don 't consume trailing commas as valid / / get soFar = ", "after the selectors  soFar.slice(match[0].length) || soFar; } groups.push((tokens = [])); } // set it to false to check whether groups are matched for the first time. // Combinators // Check to see if there are >+~ selectors or space selectors, and if there are Combinators, remove the selectors, recalculate the selectors, and push into tokens Continue to match the other simple down class/tag/id/child/PSEUDO/attr combiner if ((match = rcombinators. The exec (soFar))) {matched = match. The shift (); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace(rtrim, " ") }); soFar = soFar.slice(matched.length); } // Filters for (type in Expr.filter) { if ((match = matchExpr[type].exec(soFar)) && (! PreFilters [type] | | (match = preFilters [type] (match)))) {/ / interception selector is selected first for example. In front of the box will be removed. // groups: undefined // index: 0 // input: ". Box >.header-wrapper. Header.nav. nav-item" // length: 1 This is an array of classes matched = match.shift(); //.box /** * { * value: '.box', * type: 'class', * matches: { * 0: "box", * groups: undefined, * index: 0, * input: ".box >.header-wrapper.header.nav.nav-item ", * length: 1 this is a class array *} *} */ tokens. Push ({value: matched, type: matched) type, matches: match }); SoFar = soFar. Slice (matched. Length); } // If matched is false, this loop will be skipped. matched) { break; }}Copy the code

Select the selected element

We got an ordered array of particles in all the selectors last step, so I’m going to give you, as I said above, we’re going to pick an element and we’re going to start on the right. I’m going to select the rightmost one and then I’m going to go from right to left and verify that I’ve selected the right element. The advantages have been said. Matches: {type: ‘ID’, value: ‘header’, matches: {0: ‘header’, groups: undefined ….} }, …, { { type: ‘CLASS’, value: ‘nav-item’, matches: { 0: ‘nav-item’, groups: Undefined….}}], if the last one is a pseudo-class selector, we need to select the element in front of the pseudo-class selector and then select the type according to the pseudo-class and then filter. Ok, so if match is length 1, there’s no parallel selector. So we only have to select the elements in a set of selectors. If match[0] is longer than 2, we check if the first element in the group is an ID selector. If so, we select the element directly and change the context. For example, we preform document, and now we have an element whose ID is header, We just select the element whose ID is the header, and use it as the top and bottom, so we’re looking for less. And then we take the length of tokens, select from the last one and select the element. That’s pretty much the end of it, and all that’s left is the final procedure, which is to verify that the selected element is correct. I’ll save that for the next video.

/** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function(selector, context, results, seed) { var i, tokens, token, type, find, Already compiled in the cache = typeof selector === "function" && selector, if already compiled in the cache, if already compiled in the cache Tokenize match =! Tokenize match =! seed && tokenize((selector = compiled.selector || selector)); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees Us context) // If match === 0 is the first time, there is no seed, here we do a minimal search // Look at the lexicographs from match and parse several sets of selectors. If (match. Length === 1) {// Reduce context if the leading compound Selector is an ID // Get the first set of selectors, Array, and assign tokens. {value: "#header", type: "ID", matches: Array(1)} Tokens = match[0] = match[0]. // If the length of tokens is greater than 0, see if the first selector of tokens is of ID type. Context. nodeType = 9 document and node. Then Expr. Relativet tokens[1].type exist. Existence is a position selector. If (tokens = tokens[0]).type == "ID" && // noType === 9 of context is HTML // Relative [tokens[1].type] There are relative positions context.nodeType === 9 && documentIsHTML && Expr. Relative [tokens[1].type]) {// I'm going to get the ID here and I'm going to get this and I'm going to get this selector, # ID, and I'm going to pass in the context, the ID element as the context. context = (Expr.find["ID"](token.matches[0] .replace(runescape, funescape), context) || [])[0]; // If the ID is not present, the empty array if (! context) { return results; // The precompiled matcher will still validate the ancestor, Precompiled matchers will still verify ancestry, } else if (compiled) {context = context.parentNode; } selector = selector.slice(tokens.shift().value.length); } // The test is whether the selector needs a context, if I = 0, Fetch a seed set for right-to-left matching #box >.header-wrapper.header. nav The. Nav-item selector >.header-wrapper.header.nav. nav-item is used as the context above, and the remaining selector >.header-wrapper. If it's > start you have to know in which context. This is 0 and instead of going through the while loop, we're going to do a compilation of these selectors. i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; // If it is 0, it is 0. If it is from right to right, it is here. while (i--) { // token = tokens[i]; Tokens = tokens[I]; // Abort if we hit a combinator // Abort if (Expr. Relative [(type = tok.type)]) {break; } if ((find = Expr. Find [type])) {// search, Extend the context of the leading statistical combinator // Search, expanding context for leading sibling combinators // Expr.find["CLASS"] = support.getElementsByClassName && function(className, context) { // if (typeof context.getElementsByClassName ! == "undefined" && documentIsHTML) { // return context.getElementsByClassName(className); / / / /}}; Find (tokens. Maches [0], context) // If no element is selected, all elements will be found in the superMatch supermatcher. Matches [0].replace(runescape, funescape) if ((seed = find(// pass the class to token.matches[0].replace(runescape, funescape), / / did look have sibling selectors testing context rsibling. Test (tokens. [0] type) && testContext (context. ParentNode) | | / / or the existence of the context to context // If seed is empty or no tokens remain, we can return early // If seed is empty or has no tokens, Return // {// value: 'class', // type: 'class', // matched: [0: "header" roups: 0 input: ".header .nav .nav-item" length: 1] // } tokens.splice(i, 1); * @param {[type]} tokens [description] * @return {[type]} [description] function toSelector(tokens) {var I  = 0, len = tokens.length, selector = ""; for (; i < len; i++) { selector += tokens[i].value; } return selector; } */ / then return selector = seed. Length && toSelector(tokens); // If the selector is already parsed, return the element if (! selector) { push.apply(results, seed); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if We modified the selector // If not provided, compile and execute the filter method, provide match to avoid reparsing. If we changed the selector in the remaining tokens, Whether the selected element conforms to the rule. // Call the compile method directly if it has been compiled. Passing in parameters (seed, context,! documentIsHTML, results, The selector () && rsibling testContext (context. ParentNode) | | the context) / / if not compiled first compiled and filtering (compiled | | Compile (selector, match) (// Seed elements already have seed, context,! DocumentIsHTML, // Context is HTML // Reulst 0 quick Test has sibling elements test context results,! context || rsibling.test(selector) && testContext(context.parentNode) || context ); // Return results; };Copy the code

Sizzle source code analysis (I) Basic concepts

Sizzle source code analysis (2) tool approach

Sizzle source code analysis (iii) compatibility processing

Sizzle source code analysis (4) Sizzle static method analysis

Sizzle source code analysis (5) How to select the corresponding HTML node according to the CSS selector