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():返回的是一个函数,函数结构如下:

返回函数格式为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:

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

在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代码分析透彻,理解闭包易如反掌。本篇介绍的函数返回值都是函数,而每个返回函数需要的变量都是通过闭包存储起来,在真正执行函数的时候再读取这些变量。

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

时间: 2024-08-08 11:20:37

JQuery Sizzle引擎源代码分析的相关文章

Sizzle.filter [ 源代码分析 ]

最近的研究已Sizzle选择,对于原理中我们也不得不佩服! Sizzle中间filter办法.主要负责元素表达式过滤块的集合,在内部的方法调用Sizzle.selector.fitler滤波操作的操作方法. Sizzle.filter要点5: 1 使用LeftMatch确定表达式类型. 2 调用Sizzle.selectors.preFilter预过虑函数,运行过滤前的修正. 3 调用Sizzle.selectors.filter[ type ] 中相应的过滤函数,运行过滤操作,假设返回fals

jquery ui widget 源代码分析

jquery ui 的全部组件都是基于一个简单,可重用的widget. 这个widget是jquery ui的核心部分,有用它能实现一致的API.创建有状态的插件,而无需关心插件的内部转换. $.widget( name, base, prototype ) widget一共同拥有2或3个參数.base为可选. 这里之所以把base放在第二个參数里,主要是由于这样写代码更直观一些.(由于后面的prototype 是个代码很长的大对象). name:第一个參数是一个包括一个命名空间和组件名称的字符

jQuery中的Sizzle引擎分析

我分析的jQuery版本是1.8.3.Sizzle代码从3669行开始到5358行,将近2000行的代码,这个引擎的版本还是比较旧,最新的版本已经到v2.2.2了,代码已经超过2000行了.并且还有个专门的Sizzle主页. 从一个demo开始,HTML代码如下: <div id="grand_father"> <div id="father"> <div id="child1" class="child&

jQuery选择器引擎和Sizzle介绍

一.前言 Sizzle原来是jQuery里面的选择器引擎,后来逐渐独立出来,成为一个独立的模块,可以自由地引入到其他类库中.我曾经将其作为YUI3里面的一个module,用起来畅通无阻,没有任何障碍.Sizzle发展到现在,以jQuery1.8为分水岭,大体上可以分为两个阶段,后面的版本中引入了编译函数的概念,Sizzle的源码变得更加难读.不再兼容低版本浏览器,而且看起来更加零散.本次阅读的是Sizzle第一个阶段的最终版本jQuery1.7,从中收获颇多,一方面是框架设计的思路,另外一方面是

Jquery easyui 源代码分析之easyloader

Jquery easyui是一个javascript UI 组件库,使用它可以快速开发企业级的业务系统.如果你正准备开发系统后台,可以选择jquery easyui,也可以选择Ext JS.我个人的看法是,如果开发团队就两三个人,开发工期很短,就一两个月.那么选择jquery easyui就对了,jquery easyui源代码量不多,便于阅读和自行修改.而Ext JSy源代码太多,短时间很难看完,学习曲线也比较陡峭.如果人手充足,时间也富裕,可以考虑使用Ext JS来开发,毕竟Ext JS更强

jQuery构造函数init参数分析(二)

接着上一篇随笔讨论. 如果selector是其他字符串情况就比较多了比较复杂了 // Handle HTML strings if ( typeof selector === "string" ) {...} 开始分不同的情况处理 // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.le

Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源代码分析

上一章我们分析了Scene与Layer相关类的源代码,对Cocos2d-x的场景有了初步了解,这章我们来分析一下场景变换TransitionScene源代码. 直接看TransitionScene的定义 class CC_DLL TransitionScene : public Scene { public: /** Orientation Type used by some transitions */ enum class Orientation { /// An horizontal or

Android init源代码分析(2)init.rc解析

本文描述init.rc脚本解析以及执行过程,读完本章后,读者应能 (1) 了解init.rc解析过程 (2) 定制init.rc init.rc介绍 init.rc是一个文本文件,可认为它是Android系统启动脚本.init.rc文件中定义了环境变量配置.系统进程启动,分区挂载,属性配置等诸多内容.init.rc具有特殊的语法.init源码目录下的readme.txt中详细的描述了init启动脚本的语法规则,是试图定制init.rc的开发者的必读资料. Android启动脚本包括一组文件,包括

cocos2d-x 源代码分析 : EventDispatcher、EventListener、Event 源代码分析 (新触摸机制,新的NotificationCenter机制)

源代码版本号来自3.x,转载请注明 cocos2d-x 源代码分析总文件夹 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 1.1 结构 不详吐槽太多,也不贴图了.贴图要审核好久好久好久好久. 从小到大,先来看下Event的结构. 1.Event--------EventTouch,EventCustom,EventMouse,EventKeyboard,EventFocus,EventAcceleration 当中