博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JQuery Sizzle引擎源代码分析
阅读量:5931 次
发布时间:2019-06-19

本文共 5209 字,大约阅读时间需要 17 分钟。

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

 

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

转载地址:http://ofutx.baihongyu.com/

你可能感兴趣的文章
CCF NOI1069
查看>>
laravel5验证码
查看>>
Mac react环境搭建
查看>>
[Cpp primer] Namespace using Declarations
查看>>
图(个人复习专用)
查看>>
django ajax练习
查看>>
iis 配置域名访问
查看>>
C#串口通信实例
查看>>
TCP协议三次握手过程分析
查看>>
蓝桥杯 兰顿蚂蚁(Bfs)
查看>>
杭电1234--开门人和关门人
查看>>
杭电2029--Palindromes _easy version(回文串)
查看>>
redis的hash, list, set类型相关命令
查看>>
文件流之字节缓冲流(BufferedInputStream BufferedOutputStream)
查看>>
聪明的燕姿[JLOI2014]
查看>>
天鹅会面
查看>>
一个非科班出身程序员的成长历程
查看>>
图解Raid5数据存储的原理
查看>>
TensorFlow NormLization
查看>>
KVM克隆CentOS6虚拟机后无法启动
查看>>