视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
jQuery中选择器引擎Sizzle的解析
2020-11-27 19:33:44 责编:小采
文档


同理,在合并后,我们就知道这个合并后的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 );
     }
     }
     });
    }

    下载本文
    显示全文
    专题