jQuery技术内幕(三)

233 阅读6分钟

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

1, Sizzle选择器

Sizzle是一款纯JavaScript实现的CSS选择器引擎,它具有一下特性:

  • 完全独立,无库依赖
  • 相较于大多数常用选择器其性能非常有竞争力
  • 压缩和开启gzip后只有4KB
  • 具有高扩展性和易于使用的API
  • 支持多种浏览器 在Sizzle内部, 如果浏览器支持方法querySelectorAll(), 则调用该方法查找元素, 如果不支持, 则模拟该方法的行为, 在jQuery内部,大多数时候,总是先调用Sizzle查找元素,然后调用jQuery方法对查找结果进行操作。具体的Sizzle的总体源码结构如下:
(function() {
    // 选择器入口,查找与选择器表达式selector匹配的元素集合
    var Sizzle = function(selector, context, results, seed) { };
    // 工具方法, 排序和去重
        Sizzle.uniqueSort = function(results) {}
        // 便捷方法, 使用指定的选择器表达式expr对元素集合set进行过滤
        Sizzle.matches = function(expr, set) {}
        // 便捷方法,检查某一个元素node是否匹配选择器表达式expr
        Sizzle.matchesSelector = function(node, expr) {}
        // 内部方法,对块表达式进行查找
        Sizzle.find = function(expr, context, isXML) {}
        // 内部方法, 用块表达式过滤元素集合
        Sizzle.filter = function(expr, set, inplace, not) {}
        // 工具方法,抛出异常
        Sizzle.error = function(msg) {}
        // 工具方法,获取DOM元素集合的文本内容
        var getText = Sizzle.getText = function(elem) {}
        // 扩展方法和属性
        var Expr = Sizzle.selectors = {
            order: ["ID", "NAME", "TAG"],
            match: ( ID, CLASS, NAME, ATTR, TAG, CHILD, POS, PSEUDO),
            leftMatch: (''),
            attrMap: ('class', "for"),
            attrHandle: (href, type),
            relative: ("+", ">", "", "~"),
            find: (ID, NAME, TAG),
            preFilter: (CLASS, ID, TAG, CHILD, ATTR, PSEUDO, POS),
            filters: ( enabled, disabled, checked, selected, parent, empty, has, header,
                text, radio, checkbox, file, password, submit, image, reset, button, 
                input, focus),
            setFilters: (first, last, even, odd, it, gt, nth, eq),
            filter: (PSEUDO, CHILD, ID< TAG, CLASS, ATTR, POS)
        };
        // 如果支持方法 querySelectorAll(), 则调用该方法查找元素
        if(document.querySelectorAll) {
             (function(){
                 var oldSizzle = Sizzle;
                     Sizzle = function(query, econtext, extra, seed) {
                         // 如果上下文是document, 则直接使用querySelectorAll()查找元素
                         return makeArray(context.querySelectorAll(query), extra);
                         // 如果上下文是元素, 则为选择器表达式增加上下文, 然后调用querySelectorAll
                         return makeArray(context, querySelectorAll("[id='" + nid + 
                         "']" + query), extra);
                         // 如果查找失败,则则仍然调用oldSizzle()
                         return oldSizzle(query, conetext, extra, seed)
                     }
             })();
        }
        // 如果支持方法matchesSelector(), 则调用该方法检查元素是否匹配选择器表达式
        (function() {
             var matches = html.matchesSelector
                           || html.mozMathesSelector
                           || html.webkitMatchesSelector
                           || html.msMatchesSelector;
             // 如果支持方法matchesSelector()
             if(matches) {
                 Sizzle.matchesSelector = function(node, expr) {
                     // 尝试调用方法matchesSelector
                     var ret = matches.call(node, expr);
                     return ret;
                     // 如果查找失败,则仍然调用Sizzle()
                     return Sizzle(expr, null, null, [node]).length > 0;
                 }
             }
        })();
        // 检查浏览器是否支持getElementsByClassName()
        (function(){
            Expr.order.splice(1, 0, "CLASS");
            Expr.find.CLASS = function(match, context, isXML) {}
        })();
        //工具方法, 检查元素a是否包含元素b
        Sizzle.contains = functin(a, b) {}
})();

2, 选择器表达式

常用的表达式术语

术语说明和实例
选择器表达式CSS选择器表达式,例如: "div>p"
并列选择器表达式逗号分割的多个选择器表达式:例如: "div, p"
块表达式例如: "div>p"中的 "div", "p"
块表达式类型例如: "div"的类型是TAG, ".red"的类型是CLASS, "div.red"则是 TAG+ CLASS。共有8种块表达式类型:ID,CLASS,NAME, ATTR,TAG, CHILD, POS, PSEUDO
块间关系符表示块表达式之间关系的符号, 例如:"div>p"中的">", 共有4中块间关系符, ">"父子关系, ""祖先后代关系, "+"紧挨着的兄弟元素, "~"之后的所有兄弟元素

选择器表达式由块表达式和块间关系符组成。其中, 块表达式分为3种, 简单表达式,属性表达式,伪类表达式, 块间关系符分为4种: ">"父子关系, ""祖先后代关系, "+"紧挨着的兄弟元素, "~"之后的所有兄弟元素。

image.png

3, 选择器执行逻辑

# 1, 处理选择器表达式:解析选择器表达式中的块表达式和块间关系符
# 2, 处理块表达式:用块表达式的一部分查找,用剩余部分对查找结果进行过滤
# 3, 处理块间关系符, 按照块间关系符查找,用块表达式对查找结果进行过滤

Sizzle执行的思路 它是一款从右向左查找的选择器引擎,每一步都有核心接口

# 1, 正则chunker负责从选择器表达式中提取块表达式和块间关系符
# 2, 方法Sizzle.find(expr, context, isXML)负责查找块表达式匹配的与元素集合, 方法
Sizzle.filter(expr, set, inplace, not) 负责用块表达式过滤元素集合
# 3, 对象Sizzle.selector.relative中的块间关系过滤函数根据块间关系符过滤元素集合。

4, Sizzle(Selector, context, results, seed)

函数Sizzle(selector, context, results, seed)用于查找与选择器表达式selector匹配的元素集合。该函数式选择器引擎的入口。

函数Sizzle执行的6个关键步骤:

1, 解析块表达式和块间关系符

2, 如果存在位置伪类,则从左向右查找

​ a: 查找第一个块表达式匹配的元素集合, 得到第一个上下文元素集合

​ b: 遍历剩余的块表达式和块间关系符,不断缩小上下文元素集合

3,否则从右向左查找

​ a: 查找最后一个块表达式匹配的元素集合,得到候选集,映射集

​ b: 遍历剩余的块表达式和块间关系符,对映射集执行块间关系过滤

4,根据映射集筛选候选集,将最终匹配的元素放入结果集

5,如果存在并列选择器表达式, 则递归调用Sizzle(selector, context, results, seed)查找匹配的元素集合, 并合并排序,去重

6, 最后返回结果集

5, 方法总结

1, Sizzle(selector, context, results, seed)

用于查找与选择器表达式selector匹配的元素集合,如果浏览器支持原生方法querySelectorAll(), 则调用该方
法查找元素,如果不支持,则模拟该方法的行为

2,正则chunker

用于从选择器表达式中提取表达式和块间关系符

3,Sizzle.find(expr, context, isXML)

负责查找与块表达式匹配的元素集合, 该方法安排表达式类型数组Sizzle.selectors.order规定的查找顺序
(IDCLASSNAMETAG)挨个尝试查询,如果未找到,则查找上下文的所有后代元素(*)

4,Sizzle.fillter(expr, set, inplace, not)

负责用块表达式过滤元素集合, 该方法通过调用过滤函数集合Sizzle.selectors.filter中的过滤函数来执行过滤
操作

5,Sizzle.selectors.order

定义了查找单个块表达式时的查找顺序,依次是 ID、CLASS、NAME、TAG。其中,CLASS 需要浏览器支持方法 
getElementsByClassName()

6,Sizzle.selectors.match/leftMatch

 中存放了表达式类型和正则的映射,正则用于确定块表达式的类型,并解析其中的参数.

7,Sizzle.selectors.find

定义了IDCLASSNAMETAG 所 对 应 的 查 找 函 数。 其中,CLASS 需要浏览器支持方法 
getElementsByClassName()。查找函数会返回数组或undefined,内部通过调用相应的原生方法来查找元素

8,Sizzle.selectors.relative

存放了块间关系符和对应的块间关系过滤函数。块间关系过滤函数用于检查映射集 checkSet 中的元素是否匹配块间
关系符左侧的块表达式

9,Sizzle.selectors.preFilter

定义了CLASS、ID、TAG、CHILD、ATTR、PSEUDO、POS 所对应的预过滤函数。预过滤函用于在过滤函数之前修正与
过滤操作相关的参数

10,Sizzle.selectors.filters

定义了一组伪类和对应的伪类过滤函数。伪类过滤函数负责检查元素是否匹配伪类,返回一个布尔值

11,Sizzle.selectors.setFilters

中定义了一组位置伪类和对应的伪类过滤函数。位置伪类
过滤函数通过比较下标来确定元素在集合中的位置,返回一个布尔值

12,Sizzle.selectors.filter

定义了 PSEUDOCHILDIDTAGCLASSATTRPOS
对应的过滤函数。过滤函数负责检查元素是否匹配过滤表达式,返回一个布尔值