zoukankan      html  css  js  c++  java
  • JQuery Sizzle引擎源代码分析

        最近在拜读艾伦慕课网上写的JQuery课程,感觉在国内对JQuery代码分析透彻的人没几个能比得过艾伦。有没有吹牛?是不是我说大话了?

    什么是Sizzle引擎?

        我们经常使用JQuery的选择器查询元素,查询的选择器有简单也有复杂:
        简单点:“div”、“.navi”、“div.navi”。

        复杂点:"div input[type='checkbox']"、"div.navi + .section p"。

       

        Query实现查询时也是优先使用DOM标准查询函数,例如:

        document.getElementById()

        document.getElementsByTagName()

        document.getElementsByClassName()

        document.getElementsByName()

        高级浏览器还实现了:

        querySelector()

        querySelectorAll()

        由于浏览器版本差异导致的兼容问题,上面的函数并不是所有浏览器都支持。但JQuery得解决这些问题,所以就引入了Sizzle引擎。
    JQuery在筛选元素时优先使用浏览器自带的高级查询函数,因为查询效率高。其次才选择使用Sizzle引擎筛选元素。

        Sizzle引擎的目的是根据传入的selector选择器筛选出元素集合。执行过程经过词法分析、编译过程。通过词法分析把一个selector字符串分解成结构化的数据以便编译过程使用。编译过程充分利用了Javascript的闭包功能,生成一个函数链,在最终匹配时再去执行这个函数链。

        举个例子,一个选择器selector的值为”Aaron input[name=ttt]”,通过词法分析,得到一个结构化数组:

    [
        {
            matches: ["div"],
            type: "TAG",
            value: "Aaron"
        },
        {
            type: " ",
            value: " "
        },
        {
            matches: ["name", "=", "ttt"],
            type: "ATTR",
            value: "[name=ttt]"
        }
    ]

        selector中的input作为一个种子集合seed。意思是Sizzle根据input查询出所有input元素,结果存放到seed集合,编译过程都是在seed集合中查询过滤。
        上面说的很粗糙,不便于理解,接下来我们就拿代码来介绍。

    通过代码分析原理

        申明:下面的代码来源于Aaron在慕课网上的Jquery教程

        compile

    /**
     * 编译过程
     */
    function compile(){
        var seed = document.querySelectorAll("input"),
            selector = "Aaron [name=ttt]",
            elementMatchers = [],
            match = [
                {
                    matches: ["div"],
                    type: "TAG",
                    value: "Aaron"
                },
                {
                    type: " ",
                    value: " "
                },
                {
                    matches: ["name", "=", "ttt"],
                    type: "ATTR",
                    value: "[name=ttt]"
                }
            ];
        elementMatchers.push(matcherFromTokens(match));
        //超级匹配器
        var cached = matcherFromGroupMatchers(elementMatchers);
        var results = cached(seed);
        results[0].checked = 'checked';
    }

        JQuery的compile函数包含了所有的执行过程,由于本篇介绍的重点是编译过程,所以词法分析的过程未包含,这里直接写了match结果,实际JQuery会调用tokenize()函数获取词组。
        函数中调用了两个函数:matcherFromTokens()和matcherFromGroupMatchers()。
        matcherFromTokens():返回的是一个函数,函数结构如下:

    image

        返回函数格式为function(elem, context, xml),并且这个函数返回一个bool值表示elem是否匹配有效。

        matcherFromGroupMatchers():函数代码很简单,遍历seed集合,每个元素都调用elementMatcher函数。最终返回一个匹配成功的元素集合。
        由于matcherFromGroupMatchers()函数比较简单,所以就先介绍它。

       

        matcherFromGroupMatchers

    function matcherFromGroupMatchers(elmentMatchers){
        return function(seed){
            var results = [];
            var matcher, elem;
            for(var i = 0; i < seed.length; i++){
                var elem = seed[i];
                matcher = elmentMatchers[0];
                if(matcher(elem)){
                    results.push(elem);
                }
            }
    
            return results;
        }
    }

        遍历seed元素,每一个元素都调用matcher函数,返回true则添加到results数组中。

      matcherFromTokens
    function matcherFromTokens(tokens){
        var len = tokens.length,
            matcher,
            matchers = [];
        for(var i = 0; i < len; i++){
            if(tokens[i].type === " "){
                matchers = [addCombinator(elementMatcher(matchers))];
            }else{
                matcher = filter[tokens[i].type].apply(null, tokens[i].matches);
                matchers.push(matcher);
            }
        }
    
        return elementMatcher(matchers);
    }

        整个编译的核心也就在matcherFromTokens函数中,遍历分词tokens数组,分词分两大类,关系型和非关系型。关系型包括:“ ”、“>”、“+”、“~”。 剩下的都是非关系型分词。

        每一个非关系型分词都会对应一个matcher:

    image

        第一个分词类型为TAG,在filter中找到matcher。第二个分词为关系分词,调用addCombinator合并之前的matcher。第三个分词类型为ATTR,在filter中找到matcher。最终matchers的值为:

    image

        在return的时候又调用了elementMatcher()函数,返回的结果还是一个函数。上面介绍compile函数时看到过返回的函数结构。
        matcherFromTokens函数体中有用到addCombinator()和elementMatcher()函数以及filter对象。先看filter:

    var filter = {
        ATTR: function(name, operator,check){
            return function(elem){
                var attr = elem.getAttribute(name);
                if(operator === "="){
                    if(attr === check){
                        return true;
                    }
                }
                return false;
            }
        },
        TAG: function(nodeNameSelector){
            return function(elem){
                return elem.nodeName && elem.nodeName.toLowerCase() === nodeNameSelector;
            }
        }
    }

        一目了然,一看就知道filter中的ATTR和TAG对应了match分词组中的type类型,所以filter对应了非关系型分词的matcher函数。

        addCombinator

    function addCombinator(matcher){
        return function(elem, context, xml){
            while(elem = elem["parentNode"]){
                if(elem.nodeType === 1)
                    //找到第一个亲密的节点,立马就用终极匹配器判断这个节点是否符合前面的规则
                    return matcher(elem);
            }
        }
    }

        addCombinator对应关系型分词matcher。本例只列举了祖先和子孙关系" "的合并,返回的结果是一个签名为function(elem, contenxt, xml)的函数,函数中elem[“parentNode”]找到文档元素类型的父节点,再调用matcher校验这个父节点是否匹配。
        所以关系型matcher函数执行的过程:先通过关系类型找到匹配元素,然后再调用matcher校验匹配结果。

        elementMatcher

    function elementMatcher(matchers){
        return matchers.length > 1 ?
            function(elem, context, xml){
                var i = matchers.length;
                while(i--){
                    if(!matchers[i](elem, context, xml)){
                        return false;
                    }
                }
                return true;
            }:
            matchers[0];
    }

        elementMatcher()函数就是干实事的家伙,它遍历matchers函数数组,执行每个matcher函数,一旦有matchers[i]返回false则整个匹配就失败了。这里需要注意的一点是i--,为什么是反序遍历?因为JQuery Sizzle匹配的原则是从右往左。由于前面的match数组是按照选择器从左往右保存的,所以这里先执行最后面的。

        上面所有的代码只是简单模拟了JQuery Sizzle引擎的执行过程,真实的源代码很复杂,估计只有大神些才能领悟透彻。大神,艾伦得算一个。

        说到闭包,如果能把JQuery Sizzle代码分析透彻,理解闭包易如反掌。本篇介绍的函数返回值都是函数,而每个返回函数需要的变量都是通过闭包存储起来,在真正执行函数的时候再读取这些变量。

       如果本篇内容对大家有帮助,请点击页面右下角的关注。如果觉得不好,也欢迎拍砖。你们的评价就是博主的动力!下篇内容,敬请期待!

  • 相关阅读:
    Maven关于web.xml中Servlet和Servlet映射的问题
    intellij idea的Maven项目运行报程序包找不到的错误
    修改Maven项目默认JDK版本
    刷题15. 3Sum
    刷题11. Container With Most Water
    刷题10. Regular Expression Matching
    刷题5. Longest Palindromic Substring
    刷题4. Median of Two Sorted Arrays
    刷题3. Longest Substring Without Repeating Characters
    刷题2. Add Two Numbers
  • 原文地址:https://www.cnblogs.com/w-wanglei/p/5914724.html
Copyright © 2011-2022 走看看