jQuery 源码系列(五)sizzle 后续

欢迎来我的专栏检察系列文章。

select 函数

前面已引见了 tokensize 函数的功用,已天生了一个 tokens 数组,而且对它的构成我们也做了引见,下面就是引见对这个 tokens 数组怎样处置惩罚。

《jQuery 源码系列(五)sizzle 后续》

DOM 元素之间的衔接关联也许有 > + ~ 几种,包含空格,而 tokens 数组中是 type 是有 tag、attr 和衔接符之分的,辨别它们 Sizzle 也是有一套划定规矩的,比如上一章我们所讲的 Expr 对象,它真的非常重要:

Expr.relative = {
  ">": { dir: "parentNode", first: true },
  " ": { dir: "parentNode" },
  "+": { dir: "previousSibling", first: true },
  "~": { dir: "previousSibling" }
};

Expr.relative 标记用来将衔接符辨别,对其品种又依据目次举行分别。

如今我们再来理一理 tokens 数组,这个数组现在是一个多重数组,如今不斟酌逗号的状况,暂定只要一个分支。假如我们运用从右向左的婚配体式格局的话,div > div.seq h2 ~ p,会先获得 type 为 TAG 的 token,而关于 type 为 ~ 的 token 我们已可以用 relative 对象来推断,如今来引见 Expr.find 对象:

Expr.find = {};
Expr.find['ID'] = function( id, context ) {
  if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
    var elem = context.getElementById( id );
    return elem ? [ elem ] : [];
  }
};
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
  if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
    return context.getElementsByClassName( className );
  }
};
Expr.find["TAG"] = function(){...};

实际上 jQuery 的源码还斟酌到了兼容性,这里以 find[“ID”] 引见:

if(support.getById){
  Expr.find['ID'] = function(){...}; // 上面
}else{
  // 兼容 IE 6、7
  Expr.find["ID"] = function( id, context ) {
    if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
      var node, i, elems,
        elem = context.getElementById( id );

      if ( elem ) {

        // Verify the id attribute
        node = elem.getAttributeNode("id");
        if ( node && node.value === id ) {
          return [ elem ];
        }

        // Fall back on getElementsByName
        elems = context.getElementsByName( id );
        i = 0;
        while ( (elem = elems[i++]) ) {
          node = elem.getAttributeNode("id");
          if ( node && node.value === id ) {
            return [ elem ];
          }
        }
      }

      return [];
    }
  };
}

可以对 find 对象举行简化:

Expr.find = {
  "ID": document.getElementById,
  "CLASS": document.getElementsByClassName,
  "TAG": document.getElementsByTagName
}

今后还会引见 Expr.filter

select 源码

源码之前,来看几个正则表达式。

var runescape = /\\([\da-f]{1,6}[\x20\t\r\n\f]?|([\x20\t\r\n\f])|.)/gi
//这个正则是用来对转义字符特别处置惩罚,带个反斜杠的 token
runescape.exec('\\ab'); //["\ab", "ab", undefined]
var rsibling = /[+~]/; //婚配 +、~

matchExpr['needsContext'] = /^[\x20\t\r\n\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\([\x20\t\r\n\f]*((?:-\d)?\d*)[\x20\t\r\n\f]*\)|)(?=[^-]|$)/i
//needsContext 用来婚配不完整的 selector
matchExpr['needsContext'].test(' + p')//true
matchExpr['needsContext'].test(':first-child p')//true
//这个不完整,多是因为抽调 #ID 致使的

而关于 runescape 正则,每每都是合营 replace 来运用:

var str = '\\ab';
str.replace(runescape, funescape);
var funescape = function (_, escaped, escapedWhitespace) {
  var high = "0x" + escaped - 0x10000;
  // NaN means non-codepoint
  // Support: Firefox<24
  // Workaround erroneous numeric interpretation of +"0x"
  return high !== high || escapedWhitespace ? escaped : high < 0 ?
  // BMP codepoint
  String.fromCharCode(high + 0x10000) :
  // Supplemental Plane codepoint (surrogate pair)
  String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00);
}

我完整看不懂啦,你们本身领悟去吧,O(∩_∩)O哈哈~

var 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 || [];

  // 长度为 1,即示意没有逗号,Sizzle 尝试对此状况优化
  if (match.length === 1) {
    tokens = match[0] = match[0].slice(0);
    // 第一个 TAG 为一个 ID 挑选器,设置疾速查找
    if (tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[tokens[1].type]) {
      //将新 context 设置成谁人 ID
      context = (Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [])[0];
      if (!context) {
        // 第一个 ID 都找不到就直接返回
        return results;

      // 此时 selector 为 function,应该有特别用处
      } else if (compiled) {
        context = context.parentNode;
      }

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

    // 在没有 CHILD 的状况,从右向左,仍然是对机能的优化
    i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
    while (i--) {
      token = tokens[i];

      // 遇到 +~ 等标记先住手
      if (Expr.relative[(type = token.type)]) {
        break;
      }
      if ((find = Expr.find[type])) {
        // Search, expanding context for leading sibling combinators
        if ((seed = find(
        token.matches[0].replace(runescape, funescape), rsibling.test(tokens[0].type) && testContext(context.parentNode) || context))) {
          // testContext 是推断 getElementsByTagName 是不是存在
          // If seed is empty or no tokens remain, we can return early
          tokens.splice(i, 1);
          selector = seed.length && toSelector(tokens);
          //selector 为空,示意到头,直接返回
          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
  (compiled || compile(selector, match))(
  seed, context, !documentIsHTML, results, !context || rsibling.test(selector) && testContext(context.parentNode) || context);
  return results;
}

toSelector 函数是将 tokens 撤除已挑选的将剩下的拼接成字符串:

function toSelector(tokens) {
  var i = 0,
    len = tokens.length,
    selector = "";
  for (; i < len; i++) {
    selector += tokens[i].value;
  }
  return selector;
}

在末了又多出一个 compile 函数,是 Sizzle 的编译函数,下章讲。

到现在为止,该优化的都已优化了,selector 和 context,另有 seed,而且假如实行到 compile 函数,这几个变量的状况:

  1. selector 能够已不上最初谁人,经由种种去头去尾;

  2. match 没变,还是 tokensize 的效果;

  3. seed 事种子鸠合,一切守候婚配 DOM 的鸠合;

  4. context 能够已是头(#ID);

  5. results 没变。

能够,你也发现了,实在 compile 是一个异步函数 compile()()

总结

select 也许干了几件事,

  1. 将 tokenize 处置惩罚 selector 的效果赋给 match,所以 match 实为 tokens 数组;

  2. 在长度为 1,且第一个 token 为 ID 的状况下,对 context 举行优化,把 ID 婚配到的元素赋给 context;

  3. 若不含 needsContext 正则,则天生一个 seed 鸠合,为一切的最右 DOM 鸠合;

  4. 末了事 compile 函数,参数真多…

参考

jQuery 2.0.3 源码剖析Sizzle引擎 – 剖析道理

本文在 github 上的源码地点,欢迎来 star。

欢迎来我的博客交换。

    原文作者:songjz
    原文地址: https://segmentfault.com/a/1190000008365784
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞