zoukankan      html  css  js  c++  java
  • Sizzle一步步实现所有功能(层级选择)

    第二步:实现Sizzle("el,el,el..."),Sizzle("el > el"),Sizzle("el el"),Sizzle("el + el"),Sizzle("el ~ el")

      1 (function( window ){
      2       
      3 var arr = [];
      4 var select ;
      5 var Expr;
      6 var push = arr.push;
      7 // http://www.w3.org/TR/css3-selectors/#whitespace
      8 // 各种空白待穿正则字符串
      9 var whitespace = "[\x20\t\r\n\f]";
     10 // 带空格选择器正则,记忆无空格选择器
     11 // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
     12 var    identifier = "(?:\\.|[\w-]|[^\x00-\xa0])+";
     13 // 属性选择器: http://www.w3.org/TR/selectors/#attribute-selectors
     14 var    attributes = "\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
     15         // Operator (capture 2)
     16         "*([*^$|!~]?=)" + whitespace +
     17         // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
     18         "*(?:'((?:\\.|[^\\'])*)'|"((?:\\.|[^\\"])*)"|(" + identifier + "))|)" + whitespace +
     19         "*\]";
     20 var rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\])(?:\\.)*)" + whitespace + "+$", "g" );
     21 // 快速选择器正则 ID 或者 TAG(包括*) 或者 CLASS 选择器
     22 var rquickExpr = /^(?:#([w-]+)|(w+|*)|.([w-]+))$/;
     23 // 连接符号
     24 var rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" );
     25 // 层级符号正则'>',' ','+','~'
     26 var rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" );
     27 var matchExpr = {
     28         "ID": new RegExp( "^#(" + identifier + ")" ),
     29         "CLASS": new RegExp( "^\.(" + identifier + ")" ),
     30         "TAG": new RegExp( "^(" + identifier + "|[*])" ),
     31 };
     32 // 浏览器代码正则
     33 var rnative = /^[^{]+{s*[native w/;
     34 // token缓存
     35 var tokenCache = createCache();
     36 // 编译缓存
     37 var compilerCache = createCache();
     38 // 入口
     39 function Sizzle( selector ){
     40     // 清除空格
     41     selector = selector.replace( rtrim, "$1" )
     42     var results = [];
     43     var match;
     44     var matcher;
     45     var elem;
     46     var m;
     47     var context = document;
     48     
     49     // 是否为最简选择器
     50     if( match = rquickExpr.exec( selector )){
     51         // Sizzle('#ID)
     52         if ( (m = match[1]) ) {
     53             elem = context.getElementById( m );
     54             if( elem ){
     55                 results.push( elem );
     56             }
     57             return results;
     58             
     59         // Sizzle("TAG")
     60         }else if( (m = match[2]) ){
     61             push.apply( results, context.getElementsByTagName( selector ) );
     62             return results;
     63         
     64         // Sizzle(".CLASS")    
     65         }else if( (m = match[3]) ){
     66             // 支持getElementsByClassName
     67             if( support.getElementsByClassName ){
     68                 push.apply( results, context.getElementsByClassName( m ) );
     69                 return results;
     70             }
     71         }
     72     }
     73     // 复杂选择调到select
     74     return select( selector, context, results);
     75 }
     76 // 创建缓存函数
     77 function createCache() {
     78     var keys = [];
     79 
     80     function cache( key, value ) {
     81         // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
     82         if ( keys.push( key + " " ) > 10 ) {
     83             // Only keep the most recent entries
     84             delete cache[ keys.shift() ];
     85         }
     86         return (cache[ key + " " ] = value);
     87     }
     88     return cache;
     89 }
     90 // 错误函数
     91 Sizzle.error = function( msg ) {
     92     throw new Error( "Syntax error, unrecognized expression: " + msg );
     93 };
     94 // 版本支持变量的对外访问入口
     95 var support = Sizzle.support = {};
     96 
     97 // 判断是否支持getElementsByClassName
     98 // 支持: IE<9
     99 support.getElementsByClassName = rnative.test( document.getElementsByClassName );
    100 // 表达式对象
    101 // 存放各类相对位置,各种查询函数,各种过滤函数等。
    102 Expr = {
    103     relative: {
    104         ">": { dir: "parentNode", first: true },
    105         " ": { dir: "parentNode" },
    106         "+": { dir: "previousSibling", first: true },
    107         "~": { dir: "previousSibling" }
    108     },
    109     filter: {
    110         "TAG": function( nodeNameSelector ) {
    111             var nodeName = nodeNameSelector.toLowerCase();
    112             return nodeNameSelector === "*" ?
    113                 function() { return true; } :
    114                 function( elem ) {
    115                     return elem.nodeName.toLowerCase() === nodeName;
    116                 };
    117         },
    118         "CLASS": function( className ) {
    119             var className = className.toLowerCase();
    120             return function( elem ) {
    121                     return elem.className.toLowerCase() === className;
    122             };
    123         }
    124     },
    125     find: {
    126         "TAG": function( tag, context ) {
    127             return context.getElementsByTagName( tag );
    128         },
    129         "CLASS": support.getElementsByClassName&&function( tag, context ) {
    130             return context.getElementsByClassName( tag );
    131         },
    132     },
    133 }
    134 // tokenize函数
    135 // 将选择器字符串转化为方便使用的数组对象形式
    136 tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
    137     var cached = tokenCache[ selector + " " ];
    138     
    139     // cached.slice生成新的数组,对其修改不会修改其引用缓存
    140     if ( cached ) {
    141         return cached.slice( 0 );
    142     }
    143     // 循环条件
    144     var soFar = selector;
    145     // 结果数组
    146     var groups = [];
    147     // 匹配参数
    148     var matched;
    149     // 一个独立的tokens
    150     var tokens;
    151     // 辅助变量
    152     var match;
    153 
    154     while ( soFar ) {
    155 
    156         //首次默认创建一个tokens
    157         //之后每碰到一个逗号新增一个新的tokens
    158         if ( !matched || (match = rcomma.exec( soFar )) ) {
    159             if ( match ) {
    160                 // Don't consume trailing commas as valid
    161                 soFar = soFar.slice( match[0].length ) || soFar;
    162             }
    163             groups.push( (tokens = []) );
    164         }
    165         
    166         matched = false;
    167 
    168         // 关系token
    169         if ( (match = rcombinators.exec( soFar )) ) {
    170             matched = match.shift();
    171             tokens.push({
    172                 value: matched,
    173                 // Cast descendant combinators to space
    174                 type: match[0].replace( rtrim, " " )
    175             });
    176             soFar = soFar.slice( matched.length );
    177         }
    178 
    179         // TAG,CLASS,ID token
    180         for ( type in Expr.filter ) {
    181             if ( match = matchExpr[ type ].exec( soFar ) ) {
    182                 matched = match.shift();
    183                 tokens.push({
    184                     value: matched,
    185                     type: type,
    186                     matches: match
    187                 });
    188                 soFar = soFar.slice( matched.length );
    189             }
    190         }
    191         // 一次循环到这里三个条件都不符合没有匹配结果时,跳出。
    192         if ( !matched ) {
    193             break;
    194         }
    195     }
    196 
    197     // 意外跳出,soFar存在,报错。
    198     return soFar ?
    199             Sizzle.error( selector ) :
    200             // 缓存后转成新数组返回(预防修改缓存内容)
    201             tokenCache( selector, groups ).slice( 0 );
    202 };
    203 // 将tokens转化为selector字符串形式。
    204 function toSelector( tokens ) {
    205     var i = 0,
    206         len = tokens.length,
    207         selector = "";
    208     for ( ; i < len; i++ ) {
    209         selector += tokens[i].value;
    210     }
    211     return selector;
    212 }
    213 // !addCombinator
    214 // 增加关系处理函数
    215 // 返回关系函数,主要功能是,遍历种子节点的关系节点。
    216 // 比如li>a,传入无数个种子节点a,a.parentNode,再执行matcher,matcher里再判断这个父亲节点是不是li
    217 function addCombinator( matcher, combinator ) {
    218     var dir = combinator.dir;
    219     return combinator.first ?
    220         function( elem, context ) {
    221             while( (elem = elem[ dir ]) ){
    222                 if ( elem.nodeType === 1 ) {
    223                     return matcher( elem, context );
    224                 }
    225             }
    226         }:
    227         function( elem, context ) {
    228             while ( (elem = elem[ dir ]) ) {
    229                 if ( elem.nodeType === 1 ) {
    230                     if(matcher( elem, context )) {
    231                         return true;
    232                     }
    233                 }
    234             }
    235             return false; 
    236         }
    237 }
    238 
    239 // !elementMatcher
    240 // 生成matchers遍历器
    241 // matchers数组存放我要过滤的函数,这个函数遍历所有过滤函数,一个不符合就返回false。
    242 function elementMatcher( matchers ) {
    243     return function( elem, context ) {
    244         var i = matchers.length;
    245         while ( i-- ) {
    246             if ( !matchers[i]( elem, context ) ) {
    247                 return false;
    248             }
    249         }
    250         return true;
    251     };
    252 }
    253 // !matcherFromTokens
    254 // 根据tokens,生成过滤一组函数matchers,供elementMatcher使用
    255 // 返回的是一个执行所有过滤函数的函数
    256 function matcherFromTokens( tokens ){
    257     var matchers = [];
    258     var matcher;
    259     var i = 0;
    260     var len = tokens.length;
    261     for ( ; i < len; i++ ) {
    262         if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
    263             matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
    264         } else {
    265             matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
    266             matchers.push( matcher );
    267         }
    268     }
    269     return elementMatcher( matchers );
    270 }
    271 // !matcherFromGroupMatchers
    272 // 返回超级匹配器,
    273 function matcherFromGroupMatchers( elementMatchers ){
    274     // !!最重要superMatcher,也是最核心的函数,其它的函数为它服务。
    275     // 获取种子元素,遍历所有种子元素。
    276     // 遍历elementMatchers
    277     // 符合的推入结果数组
    278     // 一个选择器(逗号隔开的)生成一个elementMatcher,elementMatchers是存放所有elementMatcher的数组
    279     var superMatcher = function( seed, context, results) {
    280         var elems = seed || Expr.find["TAG"]( "*", document );
    281         var len = elems.length;
    282         var i = 0;
    283         for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
    284             j = 0;
    285             while ( (matcher = elementMatchers[j++]) ) {
    286                 if ( matcher( elem, context) ) {
    287                     results.push( elem );
    288                     break;
    289                 }
    290             }
    291         }
    292     }
    293     return superMatcher;
    294 }
    295 // compile
    296 // 最初的编译器,存放elementMatchers,缓存超级匹配函数并返回
    297 compile = Sizzle.compile = function( selector, match ) {
    298     var i;
    299     var elementMatchers = [];
    300     var    cached = compilerCache[ selector + " "];
    301     if ( !cached ) {
    302         i = match.length;
    303         while ( i-- ) {
    304             elementMatchers.push( matcherFromTokens( match[i] ) );
    305         }
    306         cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers ));
    307     }
    308     return cached;
    309 }
    310 // select
    311 // 兼容的自写的选择器
    312 select = Sizzle.select = function( selector, context, results){
    313     var token;
    314     var seed;
    315     var tokens;
    316     var find;
    317     var match = tokenize( selector )
    318     if ( match.length === 1 ) {
    319         // tokens
    320         var tokens = match[0].slice( 0 );
    321         // 如果tokens的首项是ID,将其设置为上下文
    322         if ( (token = tokens[0]).type === 'ID' ){    
    323             context = document.getElementById(token.matches[0]);
    324             selector = selector.slice( tokens.shift().value.length );
    325         }
    326         // 生成种子seed
    327         // 如"div ul li",所谓种子就是所有的li
    328         // 后面编译函数需要过滤出符合祖先是ul,ul的祖先是div的节点
    329         i = tokens.length;
    330         while ( i-- ){
    331             token = tokens[i];
    332             if ( Expr.relative[ (type = token.type) ] ) {
    333                 break;
    334             }
    335             if((find =  Expr.find[ type ]))
    336                 if( seed = find( token.matches[0],context ) ) {
    337                     tokens.splice( i, 1 );
    338                     selector = toSelector( tokens )
    339                     break;
    340                 }
    341         } 
    342     };
    343     // 根据selector,match生成superMatcher并调用
    344     compile( selector, match )( seed, context, results );
    345     return results;
    346 }
    347 
    348 // 对外入口
    349 window.MSizzle = Sizzle;
    350 
    351 })(window)
    352 // 测试
    353 console.log(MSizzle("ul.topnav > li"))
    354 console.log(MSizzle("ul.topnav   li"))
    355 console.log(MSizzle("ul.topnav + div"))
    356 console.log(MSizzle("ul.topnav ~ div"))

     1.先来整体流程,首先select生成种子seed,然后执行complie编译出超级匹配函数。在complie中,调用matchFromTokens生成每个tokens的匹配函数,如ul>li,div,会生成两个匹配函数,然后存入到elmentMatchers数组。然后,在matchFromTokens,根据tokens,返回匹配函数,elmentMatcher函数,和addCompinator是其辅助函数。然后回到complie中,缓存matcherFromGroupMatchers函数的结果并返回。在matcherFromGroupMatchers的返回superMatcher 函数中,遍历所有种子元素(不存在时的种子元素就是所有节点),利用elmentMatchers数组的匹配函数匹配。符合推入到结果数组中。跳回最初的complie返回的超级匹配函数,传入参数运行。

    2.matchFromTokens如何工作。假如我们有tokens,[{type:'CLASS',value:"f"},{type:'CLASS',value:"box"}],seed是一群div节点,我们只要根据tokens生成两个函数,一个matchers数组中,一个函数是当当前节点的className是f时返回true,一个函数是当当前节点的className是box时返回true。外包一个函数elmentMatcher,参数是待匹配节点,执行数组matchers的所有的函数,但凡有一个不匹配直接返回false。

    再假设tokens[{type:'TAG',value:'ul'},{type:'>',value:' > '}],seed是一群li节点。这种情况我们需要过滤li的父亲节点是否是ul。这个找到父亲节点的函数就是addCompinator。

    3.matcherFromGroupMatchers工作。他是返回一个最终核心匹配函数,这个函数遍历所有节点,每个节点,执行返回elmentMatcher函数,如果返回为true,则存入到结果数组中。由于选择器一般也会包括几个独立的选择器,如ul,div,就是两个elmentMatcher函数存在elmentMatchers,所以还要遍历执行每一个elmentMatcher。

  • 相关阅读:
    【 React
    vue : 无法加载文件 C:UsersXXXAppDataRoaming pmvue.ps1,因为在此系统上禁止运行脚本
    web前端工程化
    node.js读写文件
    gulp简单使用
    在window里面安装ubuntu子系统并安装图形化界面
    节点操作--JavaScript
    【jQuery中css(),attr()和prop区别】
    【animation和transtion】
    【网络状态反馈码】
  • 原文地址:https://www.cnblogs.com/winderby/p/4204235.html
Copyright © 2011-2022 走看看