jQuery源码分析系列(三)Sizzle选择器引擎-下

选择函数:select()

看到select()函数,if(match.length === 1){}存在的意义是尽量简化执行步骤,避免compile()函数的调用。

简化操作同样根据tokenize()函数获得标签数组,然后对标签数组首元素是特殊ID选择器的情况进行了判断,之后对标签数组进行遍历匹配,只有当匹配到正确的选择器seed=find(){}并且token为空,此时才是找到了正确结果。

select = Sizzle.select = function (selector, context, results, seed) {
    var i, tokens, token, type, find,
        compiled = typeof selector === "function" && selector,
        match = !seed && tokenize((selector = compiled.selector || selector));

    results = results || [];

    // 试图简化操作,如果只有一个选择器并且没有种子的时候
    if (match.length === 1) {
        tokens = match[0] = match[0].slice(0);
        // 如果领头的复合选择器是ID选择器
        if (tokens.length > 2 && (token = tokens[0]).type === "ID" &&
            context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) {

            context = (Expr.find["ID"](token.matches[0]
                .replace(runescape, funescape), context) || [])[0];
            if (!context) {
                return results;

          // Precompiled matchers will still verify ancestry, so step up a level
            } else if (compiled) {
                context = context.parentNode;
            }

            selector = selector.slice(tokens.shift().value.length);
        }

        // 一般是i === tokens.length
        i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
        while (i--) {	// 从右往左遍历
            token = tokens[i];

            // 如果token的类型是相对指示符,则跳出
            if (Expr.relative[(type = token.type)]) {
                break;
            }
            // 定位find为函数find(type, context),返回值为元素,
            if ((find = Expr.find[type])) {

                // 搜索区域拓展到其兄弟区域
                if (
                    (seed = find(
                        token.matches[0].replace(runescape, funescape),
                        rsibling.test(tokens[0].type) && testContext(context.parentNode) ||
                        context)
                    )
                ) {
                    // 如果在其兄弟区域找到了此元素,则
                    tokens.splice(i, 1);
                    // 如果种子空了,或者tokens空了,则返回
                    selector = seed.length && toSelector(tokens);
                    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 above
    // 编译并执行过滤函数(如果没有提供),如果我们修改了上面的选择器,请提供“match”以避免重述
    // compile(selector, match)方法调用之后返回的是一个方法:超级匹配器
    (compiled || compile(selector, match))(
        seed,
        context,
        !documentIsHTML,
        results,
        !context || rsibling.test(selector) && testContext(context.parentNode) || context
    );
    return results;
};

编译函数:compile()

compile()调用则要复杂的多,其首先要根据选择器获得标签数组,针对标签数组内不同元素,其要制定的不同的拦截策略,再将拦截器传入终极匹配器,在终极匹配器中,根据上下文或document找到所有备选种子(DOM元素),用匹配器匹配每一个备选种子,最终得到符合条件的要素。

compile = Sizzle.compile = function (selector, match /* Internal Use Only */) {
    var i,
        setMatchers = [],
        elementMatchers = [],
        cached = compilerCache[selector + " "];		// 查看是否存在缓存

    if (!cached) {

        // 创建一个递归检查所有元素的方法
        if (!match) {
            match = tokenize(selector); // 解析选择器
        }
        i = match.length;
        while (i--) {
            // 对每个解析出来的标签配置匹配器
            cached = matcherFromTokens(match[i]);
            // 根据是否包含伪类,将匹配器分为两组
            if (cached[expando]) {
                setMatchers.push(cached);
            } else {
                elementMatchers.push(cached);
            }
        }

        // Cache the compiled function
        // 缓存编译函数
        cached = compilerCache(
            selector,
            // 将两组匹配器传入,生成终极匹配器superMatche()并返回
            matcherFromGroupMatchers(elementMatchers, setMatchers)
        );

        // Save selector and tokenization
        cached.selector = selector;
    }
    // 仔细观察,最后返回的是matcherFromGroupMatchers()的superMatcher()方法
    return cached;	
};

伪类(集合)选择器:setMatcher()

为什么在compile()函数里,配置选择器时要区分开伪类和普通选择器呢?这是因为伪类和其他原子选择器最大的不同就是其位置影响其发挥作用,因此不能像以前那样从右往左解析,我们必须清楚地知道伪类的位置,然后才能正确得匹配元素。详情请点击此处

一般来说,将包含伪类的选择器分为四个部分:前置选择器伪类选择器伪类并列选择器后置选择器

// 伪类分割器
function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) {
    if (postFilter && !postFilter[expando]) {
        postFilter = setMatcher(postFilter);
    }
    if (postFinder && !postFinder[expando]) {
        postFinder = setMatcher(postFinder, postSelector);
    }
    // markFunction()标记方法,用以标注方法是否包含伪类
    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,
                []
            ),

            matcherIn = preFilter && (seed || !selector) ?
                // 通过前置匹配器preFilter先过滤出待选种子elems,把过滤结果放入matcherIn中
                // preMap存放过滤后的种子集合matcherIn在过滤前的备选种子集合elems中的位置
                condense(elems, preMap, preFilter, context, xml) :
                elems,

            matcherOut = matcher ?

                postFinder || (seed ? preFilter : preexisting || postFilter) ?

                    // ...intermediate processing is necessary
                    [] :

                    // ...otherwise use results directly
                    results :
                matcherIn;

        
        // 如果存在伪类匹配器,使用之。将满足条件节点从matcherIn中取出来存到matcherOut中
        // 这个判断阻止伪类并列匹配器和后置匹配器使用setMatcher时进入该分支
        if (matcher) {
            matcher(matcherIn, matcherOut, context, xml);
        }

        
        // 然后执行伪类并列匹配器(如果有的话),去除matcherOut中不符合条件的元素
        if (postFilter) {
            // 将matcherOut拷贝到temp中
            temp = condense(matcherOut, postMap);
            //执行匹配
            postFilter(temp, [], context, xml);

            // 不匹配的种子全部移出matcherOut,移入matcherIn
            i = temp.length;
            while (i--) {
                if ((elem = temp[i])) {
                    matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem);
                }
            }
        }

        
        // 如果种子不为空
        if (seed) {
            if (postFinder || preFilter) {
        // 最后执行后置匹配器,把matcherOut拷贝一份作为搜索范围context传入后置匹配器中执行,获取到真正的结果
                if (postFinder) {
          //获取最终matcherOut(把matcherOut置为空后插入到postFinder上下文环境中获取结果)
                    temp = [];
                    i = matcherOut.length;
                    while (i--) {
                        if ((elem = matcherOut[i])) {

                            //修复matcherIn,因为节点不是最终匹配的结果
                            temp.push((matcherIn[i] = elem));
                        }
                    }
                    //执行结果获得新的种子放入matcherOut中
                    postFinder(null, (matcherOut = []), temp, xml);
                }

                // 移动匹配元素从种子到结果来保持同步
                i = matcherOut.length;
                while (i--) {
                    if ((elem = matcherOut[i]) &&
                        (temp = postFinder ? indexOf(seed, elem) : preMap[i]) > -1) {

                        seed[temp] = !(results[temp] = elem);
                    }
                }
            }

            // 添加元素到结果中,如果定义了后置定位器则使用
        } else {
            matcherOut = condense(
                matcherOut === results ?
                    matcherOut.splice(preexisting, matcherOut.length) :
                    matcherOut
            );
            if (postFinder) {
                //将matcherOut作为搜索范围context传入
                postFinder(null, results, matcherOut, xml);
            } else { // 否则直接应用
                push.apply(results, matcherOut);
            }
        }
    });
}

终极匹配器:superMatcher()

此匹配器作为return出来的Curry化函数,最终执行在select()函数的尾部。

superMatcher = function (seed, context, xml, results, outermost) {
    var elem, j, matcher,
        matchedCount = 0,
        i = "0",
        unmatched = seed && [],
        setMatched = [],
        contextBackup = outermostContext,

        // 确定搜索范围
        // 或是参数中传递过来的备选种子seed,或是搜索范围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;
    }

    // 遍历所有种子,或上下文所有子孙节点
    for (; i !== len && (elem = elems[i]) != null; i++) {
        if (byElement && elem) {
            j = 0;

            if (!context && elem.ownerDocument != document) {
                setDocument(elem);
                xml = !documentIsHTML;
            }
            //循环执行elementMatchers中的每一组CSS的最终匹配器
            while ((matcher = elementMatchers[j++])) {
                // 传入三个参数,进行匹配,
                if (matcher(elem, context || document, xml)) {
                   // 完成elementMatchers的元素直接添加到结果中
                    results.push(elem);
                    break;
                }
            }
            if (outermost) {
                dirruns = dirrunsUnique;
            }
        }

        // 跟踪集合匹配器的不匹配元素
        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);
            }
        }
    }

    matchedCount += i;

    if (bySet && i !== matchedCount) {
        j = 0;
        // 选择器包含伪类的情况下,进入setMatchers匹配器组匹配
        while ((matcher = setMatchers[j++])) {
            matcher(unmatched, setMatched, context, xml);
        }

        if (seed) {

            //重返元素匹配,无需进行排序。matchedCount>0表示前面使用elementMatchers过程中有元素有匹配上
            //前面使用setMatchers匹配的时候备选种子集合unmatched中某个元素如果有匹配上,该元素在unmatched上的值会被赋值为false
            if (matchedCount > 0) {
                while (i--) {
                    // seed中匹配element matcher的element已被push到了results中,如果再把set matcher的匹配结果push到results里,
                    // 那么results里面element的顺序就和seed不一样了。
                    // 这段代码就是保证results里的element顺序和seed中的相同,因为setMatched和seed是长度相同且一一对应的数组。
                    if (!(unmatched[i] || setMatched[i])) {
                        setMatched[i] = pop.call(results);
                    }
                }
            }

            // 丢弃指数占位符值只得到实际的匹配
            setMatched = condense(setMatched);
        }

        //合并匹配结果
        push.apply(results, setMatched);

        // 参数去重并排序
        if (outermost && !seed && setMatched.length > 0 &&
            (matchedCount + setMatchers.length) > 1) {

            Sizzle.uniqueSort(results);
        }
    }

    // 通过嵌套的匹配覆盖全局的操控
    if (outermost) {
        dirruns = dirrunsUnique;
        outermostContext = contextBackup;
    }

    return unmatched;
};

函数Curry化:

函数curry化是一种通过把多个参数填充到函数体中,实现将函数转换为一个新的经过简化的(使之接受的参数更少)函数的技术。

function add(num1){
	return function(num2){
        return function(num3){
            return num1 + num2 + num3;
        }
    }
}
var f1 = add(2);
var f2 = f1(3);
console.log(f2(5));
console.log(add(2)(3)(5));

在此情况下,可以根据不同的需求获取某阶段的函数对象,再在需要的时候传入对应参数实现链式调用解耦合。

结语

写结语时已经是晚上十一点了,两天时间看了这么多终究是太累了,这一篇给大家完整地介绍了复合选择器的元素获取实现,想趁着今晚写完就免不了出现问题,烦请各位看官大佬评论指正!

下一篇的内容将对Sizzle篇做一个整体回顾,并设计实验对Sizzle选择器引擎进行性能分析,再从不同类选择器语句的性能排序,到出现过的一些精妙逻辑语句分析,最后到代码架构的研究,各位看官不要错过哟!

加油!! 道路是曲折的,前途是光明的!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值