微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

jQuery选择器源码解读三:tokenize方法

<div class="jb51code">
<pre class="brush:js;">
/*

  • tokenize方法是选择器解析的核心函数,它将选择器转换成两级数组groups

  • 举例:

  • 若选择器为“div.class,span”,则解析后的结果为:

  • group[0][0] = {type:'TAG',value:'div',matches:match}

  • group[0][1] = {type:'CLASS',value:'.class',matches:match}

  • group[1][0] = {type:'TAG',value:'span',matches:match}

  • 由上述结果可以看出,groups的每一个元素以逗号分隔的选择器块的解析结果,

  • 另外,上述结果中的matches等于模式匹配的结果,由于在此不方便写清楚,

  • 故只把代码matches:match写在这里。

  • tokenize方法完成如下两个主要任务:

  • 1、解析选择器

  • 2、将解析结果存入缓存中,以备后用

  • @param selector 待解析的选择器字符串

  • @param parseOnly 为true时,说明本次调用是匹配子选择器

  • 举个例子:若初始选择器为"div:not(.class:not(:eq(4))):eq(3)"

  • 代码首先匹配出TAG选择器div,

  • 之后匹配出的pseudo选择器字符串是:not(.class:not(:eq(4))):eq(3),

  • 代码会把“.class:not(:eq(4))):eq(3”作为not的括号内的值进一步进行解析,

  • 此时代码在调用tokenize解析时,parseOnly参数会传入true.
    */
    function tokenize(selector,parseOnly) {
    var matched,match,tokens,type,soFar,groups,preFilters,// 获取缓存中的结果
    cached = tokenCache[selector + " "];

    /*

    • 若缓存中有selector对应的解析结果
    • 则执行if中语句体
      */
      if (cached) {
      // 若是对初始选择器解析(parseOnly!=true),则返回缓存结果,
      // 若不是,则返回0
      return parseOnly ? 0 : cached.slice(0);
      }

    /*

    • 由于字符串在javascript中不是作为对象来处理的,
    • 所以通过赋值,代码就自动复制了一个新字符串给了soFar,
    • 这样,对soFar的任何处理都不会影响selector的原有数据
      */
      soFar = selector;
      groups = [];
      // 此处赋值,仅仅用于减少后续代码字数,缩短执行路径
      preFilters = Expr.preFilter;

    while (soFar) {

     // Comma and first run
     /*
      * rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*")
      * rcomma用来判定是否存在多个选择器块,即用逗号隔开的多个并列的选择器
      * 
      * 下面条件判定依次为:
      * !matched:若是第一次执行循环体,则为true;否则为false。
      *   这里matched即作为是否第一次执行循环体的标识,
      *   也作为本次循环中soFar是否以非法字符串(即非合法单一选择器)开头的标志。
      * (match = rcomma.exec(soFar):获取符合rcomma的匹配项
      */
     if (!matched || (match = rcomma.exec(soFar))) {
         if (match) {
             // Don't consume trailing commas as valid
             /*
              * 剔除掉第一个逗号及之前的所有字符
              * 举个例子:
              * 若初始选择器为:"div.news,span.closed",
              * 在解析过程中,首先由后续代码解析完毕div.news,剩下",span.closed"
              * 在循环体内执行到这里时,将逗号及之前之后连续的空白(match[0])删除掉,
              * 使soFar变成"span.closed",继续执行解析过程
              * 
              * 在这里,若初始选择器的最后一个非空白字符是逗号,
              * 那么执行下面代码时soFar不变,即soFar.slice(match[0].length)返回空字符串,
              * 故最终返回的是||后面的soFar
              */
             soFar = soFar.slice(match[0].length) || soFar;
         }
    
         /*
          * 在第一次执行循环体或者遇到逗号分割符时,将tokens赋值为一个空数组,
          * 同时压入groups数组
          */
         groups.push(tokens = []);
     }
    
     matched = false;
    
     // Combinators
     /*
      * rcombinators = new RegExp(
      *      "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"),* rcombinators用来匹配四种关系符,即>+~和空白
      * 
      * 若soFar中是以关系符开始的,则执行if内的语句体
      */
     if ((match = rcombinators.exec(soFar))) {
         /*
          * 将match[0]移除match数组,同时将它赋予matched
          * 若原本关系符两边带有空格,则此时match[0]与matched是不相等的
          * 举个例子:
          * 若soFar = " + .div";
          * 执行match = rcombinators.exec(soFar)后,
          * match[0] = " + ",而match[1]="+";
          * 执行完matched = match.shift()后,
          * matched=" + ",而match[0]="+";
          */
         matched = match.shift();
         // 将匹配结果压入tokens数组中
         tokens.push({
             value : matched,// Cast descendant combinators to space
             /*
              * rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)"
              *          + whitespace + "+$","g"),* whitespace = "[\\x20\\t\\r\\n\\f]";
              * 
              * 下面match[0].replace(rtrim," ")的作用是将match[0]左右两边的空白替换为空格
              * 但是由于其上的match.shift的作用,match[0]已经是两边不带空白的字符串了,
              * 故此出的替换是没有用途的<a href="https://www.jb51.cc/tag/daima/" target="_blank" class="keywords">代码</a>
              */
             type : match[0].replace(rtrim," ")
         });
    
         // 将关系符之后的字符串赋予soFar,继续解析
         soFar = soFar.slice(matched.length);
     }
    
     // Filters
     /*
      * 下面通过for语句对soFar逐一匹配ID、TAG、CLASS、CHILD、ATTR、PSEUDO类型的选择器
      * 若匹配到了,则先<a href="https://www.jb51.cc/tag/diaoyong/" target="_blank" class="keywords">调用</a>该类型选择器对应的预过滤<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>,
      * 然后,将结果压入tokens数组,继续本次循环。
      */
     for (type in Expr.filter) {
         /*
          * match = matchExpr[type].exec(soFar):对soFar<a href="https://www.jb51.cc/tag/diaoyong/" target="_blank" class="keywords">调用</a>type类型的正则表达式对soFar进行匹配,
          *  并将匹配结果赋予match。若未匹配到数据,则match为undefined。
          * !preFilters[type]:若不存在type类型的预过滤<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>,则为true
          * match = preFilters[type](match):执行预过滤,并将结果返回给match
          * 
          */
         if ((match = matchExpr[type].exec(soFar))
                 && (!preFilters[type] || (match = preFilters[type]

    (match)))) {
    // 将match[0]移除match数组,同时将它赋予matched
    matched = match.shift();
    // 将匹配结果压入tokens数组中
    tokens.push({
    value : matched,type : type,matches : match
    });
    // 将匹配结果之后的字符串赋予soFar,继续解析
    soFar = soFar.slice(matched.length);
    }
    }

     /*
      * 若matched==false,
      * 则说明本次循环没有有效的选择器(<a href="https://www.jb51.cc/tag/baokuo/" target="_blank" class="keywords">包括</a>关系符和id、class等类型选择器)
      * 因此,解析到当前位置遗留下来的soFar是非法的选择器字符串
      * 跳出while循环体
      */
     if (!matched) {
         break;
     }

    }

    // Return the length of the invalid excess
    // if we're just parsing
    // Otherwise,throw an error or return tokens
    /*

    • 若不是对初始选择器字符串进行解析(!parSEOnly==true),
    • 则返回soFar.length,此时的soFar.length代表连续有效的选择器最终位置,
    • 后续文章将以实例进行说明
    • 若是对初始选择器字符串进行解析,则看soFar是否还有字符,
    • 若是,则执行Sizzle.error(selector)抛出异常;
    • 若不是,则执行tokenCache(selector,groups).slice(0)将结果压入缓存,并返回结果的副本。
      */
      return parSEOnly ? soFar.length : soFar ? Sizzle.error(selector) :
      // Cache the tokens
      tokenCache(selector,groups).slice(0);
      }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐