This article mainly introduces the analysis of the selector engine Sizzle in jQuery. It has a certain reference value. Now I share it with everyone. Friends in need can refer to
Read the source code of Sizzle and analyze it. The Sizzle version number is 2.3.3.
Element query methods natively supported by browsers:
In Sizzle, For performance reasons, priority is given to using the native method of JS for query. Among the methods listed above, except the querySelector method, which is not used, the others are all used in Sizzle.
For cases where the results cannot be obtained directly using native methods, Sizzle needs to perform lexical analysis, decompose the complex CSS selector, and then query and filter item by item to obtain the elements that finally meet the query conditions.
There are the following points to improve the speed of this low-level query:
From right to left: The traditional selector is From left to right, for example, for the selector #box .cls a, its query process is to first find the element with id=box, and then search for the class contained in the descendant node of this element. cls element; after finding it, search all a elements under this element. After the search is completed, return to the previous level and continue to search for the next .cls element, and so on until completed. One problem with this approach is that there are many elements that do not meet the conditions and will be traversed during the search. For the right-to-left order, it first finds all a elements, and then filters out ## that meet this condition based on the remaining selector #box .cls #a element. In this way, the query scope is limited, and the speed will of course be faster relatively speaking. But one thing to be clear is that not all selectors are suitable for this right-to-left query. Not all right-to-left queries are faster than left-to-right, but it covers the vast majority of query situations.
Limited seed set: If there is only one set of selectors, that is, there is no comma-separated query condition; then the last node is searched first, and then Filter from the last node set;
Limit the query scope: If the parent node is just an ID and does not contain other restrictions, the query scope will be narrowed To the parent node;#box a;
Cache specific data: Mainly divided into three categories, tokenCache, compileCache, classCache;
Our queries for Sizzle are divided into two categories:
Simple Process (no position pseudo-class)
Query with position pseudo-class
Simple processSimple process is querying , follow the
right to left process.
Sort out the simple process Sizzle flow chart (simplified version) What the simple process ignores is mainly the processing logic related to the position pseudo-class, such as: nth-child Like Lexical analysis Lexical analysis parses the string selector into a series of TOKEN. First of all, let’s clarify the concept of TOKEN. TOKEN can be regarded as the smallest atom and cannot be split. In CSS selectors, TOKEN is generally expressed in the form of TAG, ID, CLASS, ATTR, etc. A complex CSS selector will generate a series of TOKEN after lexical analysis, and then perform final query and filtering based on these Tokens. The following is an example to illustrate the process of lexical analysis. For the parsing of the string
#box .cls a:
/**
* 下面是Sizzle中词法解析方法 tokennize 的核心代码 1670 ~ 1681 行
* soFar = '#box .cls a'
* Expr.filter 是Sizzle进行元素过滤的方法集合
* Object.getOwnPropertyNames(Expr.filter) // ["TAG", "CLASS", "ATTR", "CHILD", "PSEUDO", "ID"]
*/
for ( type in Expr.filter ) {
// 拿当前的选择字符串soFar 取匹配filter的类型,如果能匹配到,则将当前的匹配对象取出,并当做一个Token存储起来
// matchExpr中存储一些列正则,这些正则用于验证当前选择字符串是否满足某一token语法
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match ))) ) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
// 截取掉匹配到选择字符串,继续匹配剩余的字符串(继续匹配是通过这段代码外围的while(soFar)循环实现的)
// matchExpr中存储的正则都是元字符“^”开头,验证字符串是否以‘xxx’开头;这也就是说, 词法分析的过程是从字符串开始位置,从左至右,一下一下地剥离出token
soFar = soFar.slice( matched.length );
}
}
Copy after login
After the above parsing process,
#box .cls a will be parsed into an array in the following form:Sizzle: tokens
Compile functionThe process of compiling the function is very simple. First, search the corresponding matcher in the cache of the matcher according to
selector.
If the same
selector query has been performed before and the cache is still there (because the number of Sizzle changes is limited, if the number limit is exceeded, the earliest cache will be deleted), then directly return the current Cached matchers.
If not found in the cache, the ultimate matcher is generated through the
matcherFromTokens() and matcherFromGroupMatchers() methods, and the final matcher is cached.
Generate matchers based on tokens (matcherFromTokens) This step is to generate matchers (matchers) based on the tokens produced by lexical analysis.
In Sizzle, the corresponding method is matcherFromTokens.
Get a vaccination. This method is very troublesome to read.
In the Sizzle source code (
sizzle.js file) lines 1705 ~ 1765, there are only 60 lines, but a lot of factory methods are included (just Only refers to the method whose return value is Function type). Let’s simplify the process of this method (removing the processing of pseudo-class selectors)
Question: Why if we encounter the ancestor/brother relationship ('>', ' ', ' ', '~'), do you need to merge the previous matchers?
Answer:The purpose is not necessarily to merge, but to find the associated nodes of the current node (satisfying the ancestor/sibling relationship ['>', ' ', ' ', '~' ]), and then use the previous matcher to verify whether the associated node satisfies the matcher. In the "verification" step, it is not necessary to merge the previous matchers, but the merged structure will be clearer. for example:
位置伪类的查询思路,是先进行位置伪类之前的查询.mark li.limark,这个查询过程当然也是利用之前讲过的简易流程(Sizzle(selector))。查询完成后,再根据位置伪类进行过滤,留下满足位置伪类的节点。如果存在第三个条件,则利用第三个条件,再进行一次过滤。然后再利用这些满足位置伪类节点作为context,进行位置伪类之后选择器 a span的查询。
上例选择器中只存在一个位置伪类;如果存在多个,则从左至右,会形成一个一个的层级,逐个层级进行查询。
下面是对应的是matcherFromTokens()方法中对位置伪类处理。
// 这个matcherFromTokens中这个for循环,之前讲过了,但是 有个地方我们跳过没讲
for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
} else {
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
// Return special upon seeing a positional matcher
// 这个就是处理位置伪类的逻辑
if ( matcher[ expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) { // 寻找下一个关系节点位置,并用j记录下来
if ( Expr.relative[ tokens[j].type ] ) {
break;
}
}
return setMatcher(// setMatcher 是生成位置伪类查询的工厂方法
i > 1 && elementMatcher( matchers ), // 位置伪类之前的matcher
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" ), // 位置伪类之前的selector
matcher, // 位置伪类本身的matcher
i < j && matcherFromTokens( tokens.slice( i, j ) ), // 位置伪类本身的filter
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), // 位置伪类之后的matcher
j < len && toSelector( tokens ) // 位置伪类之后的selector
);
}
matchers.push( matcher );
}
}
The above is the detailed content of Analysis of Sizzle, the selector engine in jQuery. For more information, please follow other related articles on the PHP Chinese website!
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn