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。