jQuery의 선택기 엔진인 Sizzle 분석

不言
풀어 주다: 2018-07-14 09:30:51
원래의
1937명이 탐색했습니다.

이 글은 주로 jQuery의 선택기 엔진 Sizzle에 대한 분석을 소개합니다. 이제 이를 공유합니다. 도움이 필요한 친구들이 참고할 수 있습니다.

분석된 Sizzle 버전 번호는 다음과 같습니다. 2.3.3.

브라우저에서 기본적으로 지원하는 요소 쿼리 메서드:

메서드 이름 메서드 설명 호환성 설명
getElementById 요소 ID를 기반으로 한 요소 쿼리 IE 6+, 파이어폭스 2+, 크롬 4 +, Safari 3.1+
getElementsByTagName 이름을 기반으로 요소 쿼리 IE6+, Firefox 2+, Chrome 4+, Safari 3.1+
getElementsByClassName 클래스를 기반으로 요소 쿼리 IE 9 +, Firefox 3+, Chrome 4+, Safari 3.1+
getElementsByName 요소 이름 속성을 기반으로 요소 쿼리 IE10+(IE10 이하에서는 지원되지 않거나 불완전함), FireFox23+, Chrome 29+, Safari 6+
querySelector 선택기 기반 쿼리 요소 IE9+(IE8은 부분적으로 지원됨), Firefox 3.5+, Chrome 4+, Safari 3.1+
querySelectorAll 선택기 기반 쿼리 요소 IE9+(IE8은 부분적으로 지원됨), Firefox 3.5+, Chrome 4+, Safari 3.1+

In Sizzle, 성능상의 이유로 쿼리에는 JS 기본 방식을 사용하는 것이 우선입니다. 위에 나열된 메소드 중 사용되지 않는 querySelector 메소드를 제외하고 나머지는 모두 Sizzle에서 사용됩니다. querySelector方法没有被用到,其它都在Sizzle中有使用。

对于不可以使用原生方法直接获取结果的case,Sizzle就需要进行词法分析,分解这个复杂的CSS选择器,然后再逐项查询过滤,获取最终符合查询条件的元素。

有以下几个点是为了提高这种低级别查询的速度:

  • 从右至左: 传统的选择器是从左至右,比如对于选择器#box .cls a,它的查询过程是先找到id=box的元素,然后在这个元素后代节点里查找class中包含cls元素;找到后,再查找这个元素下的所有a元素。查找完成后再回到上一层,继续查找下一个.cls元素,如此往复,直至完成。这样的做法有一个问题,就是有很多不符合条件元素,在查找也会被遍历到。而对于从右向左的顺序,它是先找到所有a的元素,然后在根据剩下的选择器#box .cls,筛选出符合这个条件的a元素。这样一来,等于是限定了查询范围,相对而言速度当然会更快。但是需要明确的一点是,并不是所有的选择器都适合这种从右至左的方式查询。也并不是所有的从右至左查询都比从左至右快,只是它覆盖了绝大多数的查询情况。

  • 限定种子集合: 如果只有一组选择器,也就是不存在逗号分隔查询条件的情况;则先查找最末级的节点,在最末级的节点集合中筛选;

  • 限定查询范围: 如果父级节点只是一个ID且不包含其它限制条件,则将查询范围缩小到父级节点;#box a

  • 缓存特定数据 : 主要分三类,tokenCache, compileCache, classCache

我们对Sizzle的查询分为两类:

  1. 简易流程(没有位置伪类)

  2. 带位置伪类的查询

简易流程

简易流程在进行查询时,遵循 从右至左的流程。

梳理一下简易流程

Sizzle流程图(简易版)

简易流程忽略的东西主要是和位置伪类相关的处理逻辑,比如:nth-child之类的

词法分析

词法分析,将字符串的选择器,解析成一系列的TOKEN。

首先明确一下TOKEN的概念,TOKEN可以看做最小的原子,不可再拆分。在CSS选择器中,TOKEN的表现形式一般是TAG、ID、CLASS、ATTR等。一个复杂的CSS选择器,经过词法分析后,会生成一系列的TOKEN,然后根据这些Token进行最终的查询和筛选。

下面举个例子说明一下词法分析的过程。对于字符串#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 );
    }
}
로그인 후 복사

经过上述的解析过程后,#box .cls a会被解析成如下形式的数组:
Sizzle: tokens

编译函数

编译函数的流程很简单,首先根据selector去匹配器的缓存中查找对应的匹配器。

如果之前进行过相同selector的查询并且缓存还在(因为Sizzle换粗数量有限,如果超过数量限制,最早的缓存会被删掉),则直接返回当前缓存的匹配器。

如果缓存中找不到,则通过matcherFromTokens()matcherFromGroupMatchers() 方法生成终极匹配器,并将终极匹配器缓存。

根据tokens生成匹配器(matcherFromTokens)

这一步是根据词法分析产出的tokens,生成matchers(匹配器)。
在Sizzle中,对应的方法是matcherFromTokens

打个预防针,这个方法读起来,很费神呐。

在Sizzle源码(sizzle.js文件)中第 1705 ~ 1765 行
네이티브 메소드를 사용하여 직접 결과를 얻을 수 없는 경우 Sizzle은 어휘 분석을 수행하고 복잡한 CSS 선택기를 분해한 다음 항목별로 항목을 쿼리하고 필터링하여 최종적으로 충족하는 요소를 얻습니다. 쿼리 조건.

이 하위 수준 쿼리의 속도를 향상시키기 위한 몇 가지 사항이 있습니다:

#🎜🎜 # 오른쪽에서 왼쪽으로

: 기존 선택기는 왼쪽에서 오른쪽으로 진행됩니다. 예를 들어 #box .cls a 선택기의 경우 쿼리 프로세스는 먼저 id=box를 찾는 것입니다. 요소를 검색하고 이 요소의 하위 노드에 있는 클래스에서 cls 요소를 검색한 후 이 요소 아래의 모든 a 요소를 검색합니다. . 검색이 완료되면 이전 수준으로 돌아가 다음 .cls 요소를 계속 검색하는 방식으로 완료될 때까지 계속됩니다. 이 접근 방식의 한 가지 문제점은 조건을 충족하지 않고 검색 중에 탐색되는 요소가 많다는 것입니다. 오른쪽에서 왼쪽 순서의 경우 먼저 a의 모든 요소를 ​​찾은 다음 나머지 선택기 #box .cls를 기반으로 이 조건을 충족하는 요소를 필터링합니다. > a 요소. 이런 식으로 쿼리 범위가 제한되고 속도는 물론 상대적으로 빨라집니다. 그러나 한 가지 분명한 점은 모든 선택기가 오른쪽에서 왼쪽으로 쓰는 쿼리에 적합한 것은 아니라는 점입니다. 모든 오른쪽에서 왼쪽 쿼리가 왼쪽에서 오른쪽 쿼리보다 빠른 것은 아니지만 대부분의 쿼리 상황을 다룹니다.

  • Limited Seed set: 선택기 세트가 하나만 있는 경우, 즉 쉼표로 구분된 쿼리 조건이 없는 경우 마지막 노드가 먼저 검색됩니다. 레벨 노드는 마지막 레벨 노드 세트에서 필터링됩니다. #🎜🎜##🎜🎜#제한된 쿼리 범위 #🎜🎜#: 상위 노드가 ID가 포함되어 있지 않으면 쿼리 범위가 #box a #🎜🎜#
  • #🎜🎜##🎜🎜로 좁아집니다. #캐시별 데이터#🎜 🎜#: 주로 세 가지 범주로 구분됩니다. #🎜🎜#tokenCache#🎜🎜#, #🎜🎜#compileCache#🎜🎜#, #🎜🎜#classCache#🎜🎜# #🎜🎜# #🎜🎜# Sizzle에 대한 쿼리는 두 가지 범주로 나뉩니다. #🎜🎜#
    1. #🎜🎜#간단한 프로세스(위치 의사 없음) -class) #🎜🎜#
    2. #🎜🎜#위치 의사 클래스로 쿼리#🎜🎜#

    간단한 프로세스

    #🎜🎜#Simple 쿼리를 작성할 때 # 🎜🎜#오른쪽에서 왼쪽으로#🎜🎜# 프로세스를 따르세요. #🎜🎜##🎜🎜# 단순 프로세스 정리 #🎜🎜##🎜🎜#Sizzle 흐름도(간략화 버전) #🎜🎜##🎜🎜# 단순 프로세스가 무시하는 것은 주로 위치와 관련된 처리 로직입니다. nth-child 등과 같은 pseudo-class #🎜🎜#

    어휘 분석

    #🎜🎜# 어휘 분석은 문자열 선택기를 일련의 TOKEN으로 구문 분석합니다. #🎜🎜##🎜🎜#먼저 TOKEN의 개념을 명확히 해보자. TOKEN은 가장 작은 원자로 쪼개질 수 없다. CSS 선택자에서 TOKEN은 일반적으로 TAG, ID, CLASS, ATTR 등의 형태로 표현됩니다. 복잡한 CSS 선택기는 어휘 분석 후 일련의 TOKEN을 생성한 다음 이러한 토큰을 기반으로 최종 쿼리 및 필터링을 수행합니다. #🎜🎜##🎜🎜#다음은 어휘 분석 과정을 설명하는 예입니다. #box .cls a 문자열 구문 분석의 경우: #🎜🎜#
    function matcherFromTokens( tokens ) {
        var checkContext, matcher, j,
            len = tokens.length,
            leadingRelative = Expr.relative[ tokens[0].type ],
            implicitRelative = leadingRelative || Expr.relative[" "],
            i = leadingRelative ? 1 : 0,
    
            // The foundational matcher ensures that elements are reachable from top-level context(s)
            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循环就是根据tokens 生成matchers 的过程
        for ( ; i < len; i++ ) {
    
            // 如果碰到 祖先/兄弟 关系(&#39;>', ' ', '+', '~'),则需要合并之前的matchers;
            if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
                matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
            } else {
                matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
                matchers.push( matcher );
            }
        }
    
        // 将所有的matchers 拼合到一起 返回一个匹配器,
        // 所有的matcher返回值都是布尔值,只要有一个条件不满足,则当前元素不符合,排除掉
        return elementMatcher( matchers );
    }
    로그인 후 복사
    #🎜🎜#위 구문 분석 프로세스 후에 #box .cls a는 다음으로 구문 분석됩니다. 다음 형식의 배열: #🎜🎜#Sizzle: tokens#🎜🎜#

    함수 컴파일

    #🎜🎜#함수 컴파일 과정은 매우 간단합니다. 먼저 selector 캐시에서 해당 일치자를 찾습니다. #🎜🎜##🎜🎜#이전에도 동일한 selector 쿼리가 수행되었고 캐시가 아직 남아 있는 경우(Sizzle 변경 횟수가 제한되어 있으므로 개수 제한을 초과하면 가장 빠른 캐시 삭제됨) 현재 캐시된 일치자를 직접 반환합니다. #🎜🎜##🎜🎜#캐시에서 찾을 수 없는 경우 matcherFromTokens()matcherFromGroupMatchers() 메서드를 통해 최종 일치자가 생성되며 최종 일치자는 다음과 같습니다. 캐시된 . #🎜🎜#

    토큰 기반 매처 생성(matcherFromTokens)

    #🎜🎜#이 단계는 어휘 분석을 통해 생성된 토큰을 기반으로 매처(Matchers)를 생성하는 단계입니다. #🎜🎜#Sizzle에서 해당 메소드는 matcherFromTokens입니다. #🎜🎜##🎜🎜#예방접종을 하세요. 이 방법은 읽기가 매우 번거롭습니다. #🎜🎜##🎜🎜#Sizzle 소스 코드(sizzle.js 파일)에는 1705~1765행에 60줄만 있습니다. 많은 팩토리 메서드(반환 값이 함수 유형인 메서드만 해당) #🎜🎜# 이 메서드의 프로세스를 단순화해 보겠습니다(의사 클래스 선택기 처리 제거) #🎜🎜#
    [
        [
            { "value": "p", "type": "TAG", "matches": ["p"] }, 
            { "value": ".cls", "type": "CLASS", "matches": ["cls"] }, 
            { "value": " ", "type": " " }, 
            { "value": "input", "type": "TAG", "matches": ["input"] }, 
            { "value": "[type=\"text\"]", "type": "ATTR", "matches": ["type", "=", "text"]}
        ]
    ]
    로그인 후 복사
    로그인 후 복사
    #🎜🎜##🎜🎜#Question: #🎜🎜#조상/형제 관계가 발생하는 경우( ' >', ' ', '+', '~'), 이전 일치자를 병합해야 합니까? #🎜🎜##🎜🎜##🎜🎜#Answer:#🎜🎜#목적은 반드시 병합하는 것이 아니라 현재 노드 연관 노드를 찾는 것입니다(조상/형제 관계 ['>', ' ', '+', '~']), 그리고 이전 매처를 사용하여 연관된 노드가 매처를 만족하는지 확인합니다. "검증" 단계에서는 이전 일치자를 병합할 필요가 없지만 병합된 구조가 더 명확해집니다. 예: #🎜🎜#
    我们需要买汽车,现在有两个汽车品牌A、B。A下面有四种车型:a1,a2,a3,a4;B下面有两种车型:b1,b2。那么我们可以的买到所有车就是
    [a1,a2,a3,a4,b1,b2]。但是我们也可以这么写{A:[a1,a2,a3,a4],B:[b1,b2]}。这两种写法都可以表示我们可以买到车型。只是第二种相对前者,更清晰列出了车型所属品牌关系。

    同理,在合并后,我们就知道这个合并后的matcher就是为了验证当前的节点的关联节点。

    生成终极匹配器(matcherFromGroupMatchers)

    主要是返回一个匿名函数,在这个函数中,利用matchersFromToken方法生成的匹配器,去验证种子集合seed,筛选出符合条件的集合。
    先确定种子集合,然后在拿这些种子跟匹配器逐个匹配。在匹配的过程中,从右向左逐个token匹配,只要有一个环节不满条件,则跳出当前匹配流程,继续进行下一个种子节点的匹配过程。

    通过这样的一个过程,从而筛选出满足条件的DOM节点,返回给select方法。

    查询过程demo

    用一个典型的查询,来说明Sizzle的查询过程。

    p.cls  input[type="text"] 为例:

    解析出的tokens:

    [
        [
            { "value": "p", "type": "TAG", "matches": ["p"] }, 
            { "value": ".cls", "type": "CLASS", "matches": ["cls"] }, 
            { "value": " ", "type": " " }, 
            { "value": "input", "type": "TAG", "matches": ["input"] }, 
            { "value": "[type=\"text\"]", "type": "ATTR", "matches": ["type", "=", "text"]}
        ]
    ]
    로그인 후 복사
    로그인 후 복사

    首先这个选择器 会筛选出所有的<input>作为种子集合seed,然后在这个集合中寻找符合条件的节点。
    在寻找种子节点的过程中,删掉了token中的第四条{ "value": "input", "type": "TAG", "matches": ["input"] }

    那么会根据剩下的tokens生成匹配器

    • matcherByTag('p')

    • matcherByClass('.cls')

    碰见父子关系' ',将前面的生成的两个matcher合并生成一个新的

    • matcher:

      • matcherByTag('p'),

      • matcherByClass('.cls')

    这个matcher 是通过addCombinator()方法生成的匿名函数,这个matcher会先根据 父子关系parentNode,取得当前种子的parentNode, 然后再验证是否满足前面的两个匹配器。

    碰见第四条 属性选择器,生成

    • matcherByAttr('[type="text"]')

    至此,根据tokens已经生成所有的matchers。

    终极匹配器

    • matcher:

      • matcherByTag('p')

      • matcherByClass('.cls')

    • matcherByAttr('[type="text"]')

    matcherFromTokens()方法中的最后一行,还有一步操作,将所有的matchers通过elementMatcher()合并成一个matcher。
    elementMatcher这个方法就是将所有的匹配方法,通过while循环都执行一遍,如果碰到不满足条件的,就直接挑出while循环。
    有一点需要说明的就是: elementMatcher方法中的while循环是倒序执行的,即从matchers最后一个matcher开始执行匹配规则。对应上面的这个例子就是,最开始执行的匹配器是matcherByAttr('[type="text"]')。 这样一来,就过滤出了所有不满足type="text"<input>的元素。然后执行下一个匹配条件,

    Question: Sizzle中使用了大量闭包函数,有什么作用?出于什么考虑的?
    Answer:闭包函数的作用,是为了根据selector动态生成匹配器,并将这个匹配器缓存(cached)。因为使用闭包,匹配器得以保存在内存中,这为缓存机制提供了支持。
    这么做的主要目的是提高查询性能,通过常驻内存的匹配器避免再次消耗大量资源进行词法分析和匹配器生成。以空间换时间,提高查询速度。

    Question: matcherFromTokens中, 对每个tokens生成匹配器列表时,为什么会有一个初始化的方法?
    Answer: 这个初始化的方法是用来验证元素是否属于当前context

    Question: matcherFromGroupMatchers的作用?
    Answer: 返回一个终极匹配器,并让编译函数缓存这个终极匹配器。 在这个终极匹配器中,会将获取到的种子元素集合与匹配器进行比对,筛选出符合条件的元素。

    TODO: 编译机制也许是Sizzle为了做缓存以便提高性能而做出的选择??
    是的,详细答案待补充~~~

    TODO: outermostContext的作用
    细节问题,还有待研究~~~


    带位置伪类的查询流程

    带位置伪类的查询是 由左至右

    用选择器.mark li.limark:first.limark2 a span举例。

    在根据tokens生成匹配器(matcherFromTokens)之前的过程,跟简易查询没有任何区别。
    不同的地方就在matcherFromTokens()方法中。位置伪类不同于简易查询的是,它会根据位置伪类将选择器分成三个部分。对应上例就是如下

    • .mark li.limark : 位置伪类之前的选择器;

    • :first : 位置伪类本身;

    • .limark2: 跟位置伪类本身相关的选择器,

    • a span:位置伪类之后的选择器;

    位置伪类的查询思路,是先进行位置伪类之前的查询.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 );
            }
        }
    로그인 후 복사

    setMatcher()方法的源码,在这里生成最终的matcher, return给compile()方法。

    //第1个参数,preFilter,前置过滤器,相当于伪类token之前`.mark li.limark`的过滤器matcher
    //第2个参数,selector,伪类之前的selector (`.mark li.limark`)
    //第3个参数,matcher,    当前位置伪类的过滤器matcher `:first`
    //第4个参数,postFilter,伪类之后的过滤器 `.limark2`
    //第5个参数,postFinder,后置搜索器,相当于在前边过滤出来的集合里边再搜索剩下的规则的一个搜索器 ` a span`的matcher
    //第6个参数,postSelector,后置搜索器对应的选择器字符串,相当于` a span`
    function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
        //TODO: setMatcher 会把这俩货在搞一次setMatcher, 还不太懂
        if ( postFilter && !postFilter[ expando ] ) {
            postFilter = setMatcher( postFilter );
        }
        if ( postFinder && !postFinder[ expando ] ) {
            postFinder = setMatcher( postFinder, postSelector );
        }
        
        return markFunction(function( seed, results, context, xml ) {
            var temp, i, elem,
                preMap = [],
                postMap = [],
                preexisting = results.length,
    
                // Get initial elements from seed or context
                elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
    
                // Prefilter to get matcher input, preserving a map for seed-results synchronization
                matcherIn = preFilter && ( seed || !selector ) ?
                    condense( elems, preMap, preFilter, context, xml ) :
                    elems,
    
                matcherOut = matcher ?
                    // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
                    postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
    
                        // ...intermediate processing is necessary
                        [] :
    
                        // ...otherwise use results directly
                        results :
                    matcherIn;
    
            // Find primary matches
            if ( matcher ) {
                // 这个就是 匹配位置伪类的 逻辑, 将符合位置伪类的节点剔出来
                matcher( matcherIn, matcherOut, context, xml );
            }
    
            // Apply postFilter
            if ( postFilter ) {
                temp = condense( matcherOut, postMap );
                postFilter( temp, [], context, xml );
    
                // Un-match failing elements by moving them back to matcherIn
                i = temp.length;
                while ( i-- ) {
                    if ( (elem = temp[i]) ) {
                        matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
                    }
                }
            }
    
            if ( seed ) {
                if ( postFinder || preFilter ) {
                    if ( postFinder ) {
                        // Get the final matcherOut by condensing this intermediate into postFinder contexts
                        temp = [];
                        i = matcherOut.length;
                        while ( i-- ) {
                            if ( (elem = matcherOut[i]) ) {
                                // Restore matcherIn since elem is not yet a final match
                                temp.push( (matcherIn[i] = elem) );
                            }
                        }
                        postFinder( null, (matcherOut = []), temp, xml );
                    }
    
                    // Move matched elements from seed to results to keep them synchronized
                    i = matcherOut.length;
                    while ( i-- ) {
                        if ( (elem = matcherOut[i]) &&
                            (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
    
                            seed[temp] = !(results[temp] = elem);
                        }
                    }
                }
    
            // Add elements to results, through postFinder if defined
            } else {
                matcherOut = condense(
                    matcherOut === results ?
                        matcherOut.splice( preexisting, matcherOut.length ) :
                        matcherOut
                );
                if ( postFinder ) {
                    postFinder( null, results, matcherOut, xml );
                } else {
                    push.apply( results, matcherOut );
                }
            }
        });
    }
    로그인 후 복사

    以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

    相关推荐:

    Debounce函数和Throttle函数的实现原理

  • 위 내용은 jQuery의 선택기 엔진인 Sizzle 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    원천:php.cn
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    최신 이슈
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿
    회사 소개 부인 성명 Sitemap
    PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!