1 (function( window, undefined ) {2
3 vari,4      cachedruns,//正在匹配第几个元素
5      Expr,     //Sizzle.selectors的快捷方式
6      getText,//获取文本函数
7      isXML,     //是否为xml
8      compile,//编译函数
9 outermostContext,10 recompare,11 sortInput,12
13      //Local document vars
14 setDocument,15      document,//这里只是document是个普通的变量,需要setDocument函数赋值处理过的真正的document
16 docElem,17 documentIsHTML,18      rbuggyQSA,//支持的querySelectorAll选择器正则(rbuggyQSA = new RegExp( rbuggyQSA.join("|") );)
19      rbuggyMatches,//支持matchesSelector正则(rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );)
20      matches,//matchesSelector函数
21 contains,22
23      //Instance-specific data
24      expando = "sizzle" + -(newDate()),25      preferredDoc = window.document,//全局的document
26      support ={},27      dirruns = 0,28      done = 0,//第几个关系选择器匹配函数
29      classCache =createCache(),30      tokenCache =createCache(),31      compilerCache =createCache(),32      hasDuplicate = false,33      sortOrder = function() { return 0; },34
35      //General-purpose constants
36      strundefined = typeofundefined,37      MAX_NEGATIVE = 1 << 31,38
39      //Array methods
40      arr =[],41      pop =arr.pop,42      push_native =arr.push,43      push =arr.push,44      slice =arr.slice,45      //Use a stripped-down indexOf if we can't use a native one
46      //如果数组原生不支持indexOf,我们自己定义。获取元素在数组中的位置
47      indexOf = arr.indexOf || function( elem ) {48           var i = 0,49                len = this.length;50           for ( ; i < len; i++) {51                if ( this[i] ===elem ) {52                     returni;53 }54 }55           return -1;56 },57
58
59      //正则表达式
60
61      //空白字符正则字符串
62      whitespace = "[\\x20\\t\\r\\n\\f]",63      //字符编码正则字符串
64      characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",65
66      identifier = characterEncoding.replace( "w", "w#"),67
68      //可用的属性操作符
69      operators = "([*^$|!~]?=)",70      //属性选择器正则
71 ///^\[whitespace*(characterEncoding)whitespace*(?:([*^$|!~]?=)whitespace*(?:(['"])((?:\\.|[^\\])*?)\3|((?:\\.|[\w#-]|[^\x00-\xa0])+)|)|)whitespace*\]/
72      attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
73           "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",74
75      //伪类正则字符串
76      pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",77
78      //去掉两端空白和字符串中的反斜杠(如果连续两个去掉一个)正则
79      rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"),80      //并联选择器的正则
81      rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*"),82      //关系选择器正则
83      rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*"),84      //伪类正则字符串
85      rpseudo = newRegExp( pseudos ),86      ridentifier = new RegExp( "^" + identifier + "$"),87
88      matchExpr ={89           "ID": new RegExp( "^#(" + characterEncoding + ")"),90           "CLASS": new RegExp( "^\\.(" + characterEncoding + ")"),91           "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]"),92           "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")"),93           "ATTR": new RegExp( "^" +attributes ),94           "PSEUDO": new RegExp( "^" +pseudos ),95           "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
96                "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
97                "*(\\d+)|))" + whitespace + "*\\)|)", "i"),98           //开始为>+~或位置伪类,如果选择器中有位置伪类解析从左往右
99           "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
100                whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i")101 },102      //弟兄正则
103      rsibling = /[\x20\t\r\n\f]*[+~]/,104      //原生函数正则
105      rnative = /^[^{]+\{\s*\[native code/,106
107      //仅仅单个id或tag、class选择器正则(用来快速解析并获取元素)
108      rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,109      //jQuery自定义的伪类,它们不是的CSS规范的一部分,使用它们查询不能充分利用原生DOM提供的querySelectorAll() 方法来提高性能。为了在现代浏览器上获得更佳的性能,请使用 .filter(":input")代替。
110      rinputs = /^(?:input|select|textarea|button)$/i,111      //同上
112      rheader = /^h\d$/i,113
114      rescape = /'|\\/g,115      //属性没带引号正则
116      rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,117
118      //css转义,配合funescape使用 如:string.replace(runescape, funescape);
119      runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,120      funescape = function( _, escaped ) {121           var high = "0x" + escaped - 0x10000;122           //NaN means non-codepoint
123           return high !== high ?
124 escaped :125                //BMP codepoint
126                high < 0 ?
127                     String.fromCharCode( high + 0x10000) :128                     //Supplemental Plane codepoint (surrogate pair)
129                     String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00);130 };131
132 //把NodeList转换为数组,因为数组比集合效率高
133 try{134 push.apply(135           (arr =slice.call( preferredDoc.childNodes )),136 preferredDoc.childNodes137 );138      //Support: Android<4.0
139      //Detect silently failing push.apply
140 arr[ preferredDoc.childNodes.length ].nodeType;141 } catch( e ) {142      push = { apply: arr.length ?
143
144           //Leverage slice if possible
145           function( target, els ) {146 push_native.apply( target, slice.call(els) );147 } :148
149           //Support: IE<9
150           //Otherwise append directly
151           function( target, els ) {152                var j =target.length,153                     i = 0;154                //Can't trust NodeList.length
155                while ( (target[j++] = els[i++]) ) {}156                target.length = j - 1;157 }158 };159 }160
161 //测试是否是原生函数
162 functionisNative( fn ) {163      return rnative.test( fn + "");164 }165
166 //创建缓存函数(缓存大小默认为50,可以自己设置)
167 //这里利用了闭包(私有变量、变量一直保存在内存中)
168 functioncreateCache() {169      varcache,170           keys =[];171
172      return (cache = function( key, value ) {173           //Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
174           if ( keys.push( key += " " ) >Expr.cacheLength ) {175                //Only keep the most recent entries
176                deletecache[ keys.shift() ];177 }178           return (cache[ key ] =value);179 });180 }181
182 //标记函数供sizzle特殊用途
183 functionmarkFunction( fn ) {184      fn[ expando ] = true;185      returnfn;186 }187
188 //用于做各种特征检测,比如是否支持某个API,API支持是否完美
189
190 functionassert( fn ) {191      var div = document.createElement("div");192
193      try{194           return !!fn( div );195      } catch(e) {196           return false;197      } finally{198           //release memory in IE
199           div = null;200 }201 }202
203 //sizzle主函数
204 //selector css选择器, context上下文,results结果集,seed筛选集
205
206 functionSizzle( selector, context, results, seed ) {207      varmatch, elem, m, nodeType,208           //QSA vars
209 i, groups, old, nid, newContext, newSelector;210
211      if ( ( context ? context.ownerDocument || context : preferredDoc ) !==document ) {212 setDocument( context );213 }214
215      context = context ||document;216      results = results ||[];217
218      if ( !selector || typeof selector !== "string") {219           returnresults;220 }221
222      if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9) {223           return[];224 }225
226      if ( documentIsHTML && !seed ) {227
228           //Shortcuts
229           //如果仅仅是id或class或tag用原生函数
230           if ( (match =rquickExpr.exec( selector )) ) {231                //Speed-up: Sizzle("#ID")
232                if ( (m = match[1]) ) {233                     if ( nodeType === 9) {234                          elem =context.getElementById( m );235                          //Check parentNode to catch when Blackberry 4.6 returns
236                          //nodes that are no longer in the document #6963
237                          if ( elem &&elem.parentNode ) {238                               //Handle the case where IE, Opera, and Webkit return items
239                               //by name instead of ID
240                               if ( elem.id ===m ) {241 results.push( elem );242                                    returnresults;243 }244                          } else{245                               returnresults;246 }247                     } else{248                          //Context is not a document
249                          if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
250                               contains( context, elem ) && elem.id ===m ) {251 results.push( elem );252                               returnresults;253 }254 }255
256                //Speed-up: Sizzle("TAG")
257                } else if ( match[2] ) {258 push.apply( results, context.getElementsByTagName( selector ) );259                     returnresults;260
261                //Speed-up: Sizzle(".CLASS")
262                } else if ( (m = match[3]) && support.getElementsByClassName &&context.getElementsByClassName ) {263 push.apply( results, context.getElementsByClassName( m ) );264                     returnresults;265 }266 }267
268           //QSA path
269           //使用querySelectorAll
270           if ( !support.qsa && !rbuggyQSA.test(selector) ) {271                old = true;272                nid =expando;273                newContext =context;274                newSelector = nodeType === 9 &&selector;275
276                //qSA works strangely on Element-rooted queries
277                //We can work around this by specifying an extra ID on the root
278                //and working up from there (Thanks to Andrew Dupont for the technique)
279                //IE 8 doesn't work on object elements
280                if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object") {281                     groups =tokenize( selector );282
283                     if ( (old = context.getAttribute("id")) ) {284                          nid = old.replace( rescape, "\\$&");285                     } else{286                          context.setAttribute( "id", nid );287 }288                     nid = "[id='" + nid + "'] ";289
290                     i =groups.length;291                     while ( i--) {292                          groups[i] = nid +toSelector( groups[i] );293 }294                     newContext = rsibling.test( selector ) && context.parentNode ||context;295                     newSelector = groups.join(",");296 }297
298                if( newSelector ) {299                     try{300 push.apply( results,301 newContext.querySelectorAll( newSelector )302 );303                          returnresults;304                     } catch(qsaError) {305                     } finally{306                          if ( !old ) {307                               context.removeAttribute("id");308 }309 }310 }311 }312 }313
314      //All others
315      return select( selector.replace( rtrim, "$1"), context, results, seed );316 }317
318 //检测是否为xml
319 isXML = Sizzle.isXML = function( elem ) {320      //documentElement is verified for cases where it doesn't yet exist
321      //(such as loading iframes in IE - #4833)
322      var documentElement = elem && (elem.ownerDocument ||elem).documentElement;323      return documentElement ? documentElement.nodeName !== "HTML" : false;324 };325
326
327 //根据当前的document设置document相关的变量(只设置一次)
328 setDocument = Sizzle.setDocument = function( node ) {329      var doc = node ? node.ownerDocument ||node : preferredDoc;330
331      //如果document已经设置了返回(这里document是个变量初始值是undefined)
332      //不是文档对象返回
333      //文档对象没有返回根节点时返回
334      if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {335           returndocument;336 }337
338      //给document变量设置真正的文档
339      document =doc;340      docElem =doc.documentElement;341
342      documentIsHTML = !isXML( doc );343
344      //检查getElementsByTagName是否会返回注释节点,IE6-8会混杂注释节点
345      support.getElementsByTagName = assert(function( div ) {346           div.appendChild( doc.createComment("") );347           return !div.getElementsByTagName("*").length;348 });349
350      //Check if attributes should be retrieved by attribute nodes
351      support.attributes = assert(function( div ) {352           div.innerHTML = "<select></select>";353           var type = typeof div.lastChild.getAttribute("multiple");354           //IE8 returns a string for some attributes even when not present
355           return type !== "boolean" && type !== "string";356 });357
358      //检查getElementsByClassName是否100%支持
359      support.getElementsByClassName = assert(function( div ) {360           //Oprea9.6不可以发现第二个className
361           div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";362           if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {363                return false;364 }365
366           //safari3.2缓存了class属性,所以不能获取改变过的classname
367           div.lastChild.className = "e";368           return div.getElementsByClassName("e").length === 2;369 });370
371      //检查getElementByName
372      support.getByName = assert(function( div ) {373           //Inject content
374           div.id = expando + 0;375           //Support: Windows 8 Native Apps
376           //Assigning innerHTML with "name" attributes throws uncatchable exceptions
377           //http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx
378           //getElementsByName是一个问题多的API
379           //1 IE6-7下getElementsByName与getElementById都不区分元素的name与ID
380           //2 IE的getElementsByName只对表单元素有效,无视拥有相同name值的span div元素
381           //3 IE6-7下即使通过document.createElement创建一个表单元素,动态设置name与插入
382           //DOM树,getElementsByName无法找到此元素,innerHTML也不行。一定需要以
383           //document.createElement("<input name="aaa"/>")方式生成元素才行。
384           //同样的情况也发生在iframe上,IE6-7的iframe的name也需要这样同时生成。
385           //4 name本来是一个property,但标准浏览器好像已经默认setAttribute("name","xxx")
386           //也能被getElementsByName获取到。
387           //5 IE6-8通过innerHTML生成包含name属性的元素时,可能发生无法捕获的错误
388           div.appendChild( document.createElement("a") ).setAttribute( "name", expando );389           div.appendChild( document.createElement("i") ).setAttribute( "name", expando );390 docElem.appendChild( div );391
392           //Test
393           var pass = doc.getElementsByName &&
394                //buggy browsers will return fewer than the correct 2
395                doc.getElementsByName( expando ).length === 2 +
396                //buggy browsers will return more than the correct 0
397                doc.getElementsByName( expando + 0).length;398
399           //Cleanup
400 docElem.removeChild( div );401
402           returnpass;403 });404
405      //Support: Webkit<537.32
406      //Detached nodes confoundingly follow *each other*
407      support.sortDetached = assert(function( div1 ) {408           return div1.compareDocumentPosition &&
409                //Should return 1, but Webkit returns 4 (following)
410                (div1.compareDocumentPosition( document.createElement("div") ) & 1);411 });412
413      //ie6/7 href和type属性获取
414      Expr.attrHandle = assert(function( div ) {415           div.innerHTML = "<a href='#'></a>";416           return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
417                div.firstChild.getAttribute("href") === "#";418      }) ?
419 {} :420 {421                "href": function( elem ) {422                     return elem.getAttribute( "href", 2);423 },424                "type": function( elem ) {425                     return elem.getAttribute("type");426 }427 };428
429     //查找id节点和节点id是否匹配过滤函数
430      if( support.getByName ) {431           Expr.find["ID"] = function( id, context ) {432                if ( typeof context.getElementById !== strundefined &&documentIsHTML ) {433                     var m =context.getElementById( id );434                     //Check parentNode to catch when Blackberry 4.6 returns
435                     //nodes that are no longer in the document #6963
436                     //Blackberry 4.6 缓存过度,即便这元素被移出DOM树也能找到
437                     return m && m.parentNode ?[m] : [];438 }439 };440           Expr.filter["ID"] = function( id ) {441                var attrId =id.replace( runescape, funescape );442                return function( elem ) {443                     return elem.getAttribute("id") ===attrId;444 };445 };446      } else{447           Expr.find["ID"] = function( id, context ) {448                if ( typeof context.getElementById !== strundefined &&documentIsHTML ) {449                     var m =context.getElementById( id );450
451                     return m ?
452                          m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
453 [m] :454 undefined :455 [];456 }457 };458           Expr.filter["ID"] =  function( id ) {459                var attrId =id.replace( runescape, funescape );460                return function( elem ) {461                     var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");462                     return node && node.value ===attrId;463 };464 };465 }466
467      //根据标签查找节点
468      Expr.find["TAG"] = support.getElementsByTagName ?
469           function( tag, context ) {470                if ( typeof context.getElementsByTagName !==strundefined ) {471                     returncontext.getElementsByTagName( tag );472 }473 } :474           function( tag, context ) {475                varelem,476                     tmp =[],477                     i = 0,478                     results =context.getElementsByTagName( tag );479
480                //Filter out possible comments
481                if ( tag === "*") {482                     while ( (elem = results[i++]) ) {483                          if ( elem.nodeType === 1) {484 tmp.push( elem );485 }486 }487
488                     returntmp;489 }490                returnresults;491 };492
493      //如果支持根据name查找节点
494      Expr.find["NAME"] = support.getByName && function( tag, context ) {495           if ( typeof context.getElementsByName !==strundefined ) {496                returncontext.getElementsByName( name );497 }498 };499
500      //如果支持根据class查找节点
501      Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {502           if ( typeof context.getElementsByClassName !== strundefined &&documentIsHTML ) {503                returncontext.getElementsByClassName( className );504 }505 };506
507      //QSA and matchesSelector support
508
509      rbuggyMatches = [];//支持的matchesSelector放到数组中
510
511      rbuggyQSA = [ ":focus" ];//支持的querySelectorAll放到数组中
512
513      if ( (support.qsa =isNative(doc.querySelectorAll)) ) {514           //Build QSA regex
515           //Regex strategy adopted from Diego Perini
516           assert(function( div ) {517                //Select is set to empty string on purpose
518                //This is to test IE's treatment of not explicitly
519                //setting a boolean content attribute,
520                //since its presence should be enough
521                //http://bugs.jquery.com/ticket/12359
522                div.innerHTML = "<select><option selected=''></option></select>";523
524                //IE8 - Some boolean attributes are not treated correctly
525                if ( !div.querySelectorAll("[selected]").length ) {526                     rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)");527 }528
529                //Webkit/Opera - :checked should return selected option elements
530                //http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
531                //IE8 throws error here and will not see later tests
532                if ( !div.querySelectorAll(":checked").length ) {533                     rbuggyQSA.push(":checked");534 }535 });536
537           assert(function( div ) {538
539                //Opera 10-12/IE8 - ^= $= *= and empty values
540                //Should not select anything
541                div.innerHTML = "<input type='hidden' i=''/>";542                if ( div.querySelectorAll("[i^='']").length ) {543                     rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')");544 }545
546                //FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
547                //IE8 throws error here and will not see later tests
548                if ( !div.querySelectorAll(":enabled").length ) {549                     rbuggyQSA.push( ":enabled", ":disabled");550 }551
552                //Opera 10-11 does not throw on post-comma invalid pseudos
553                div.querySelectorAll("*,:x");554                rbuggyQSA.push(",.*:");555 });556 }557
558      if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector ||
559           docElem.mozMatchesSelector ||
560           docElem.webkitMatchesSelector ||
561           docElem.oMatchesSelector ||
562 docElem.msMatchesSelector) )) ) {563
564           assert(function( div ) {565                //ie9能匹配移出DOM树的节点
566                support.disconnectedMatch = matches.call( div, "div");567
568                //Gecko对于非法选择符不会抛错,而是返回false
569                matches.call( div, "[s!='']:x");570                rbuggyMatches.push( "!=", pseudos );571 });572 }573
574      rbuggyQSA = new RegExp( rbuggyQSA.join("|") );575      rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );576
577      //Element contains another
578      //Purposefully does not implement inclusive descendent
579      //As in, an element does not contain itself
580      contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
581           function( a, b ) {582                var adown = a.nodeType === 9 ?a.documentElement : a,583                     bup = b &&b.parentNode;584                return a === bup || !!( bup && bup.nodeType === 1 &&(585                     adown.contains ?
586 adown.contains( bup ) :587                          a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
588 ));589 } :590           function( a, b ) {591                if( b ) {592                     while ( (b =b.parentNode) ) {593                          if ( b ===a ) {594                               return true;595 }596 }597 }598                return false;599 };600
601      //文档顺序排序
602      sortOrder = docElem.compareDocumentPosition ?
603      function( a, b ) {604
605           //重复删除标记
606           if ( a ===b ) {607                hasDuplicate = true;608                return 0;609 }610
611           var compare = b.compareDocumentPosition && a.compareDocumentPosition &&a.compareDocumentPosition( b );612
613           if( compare ) {614                //Disconnected nodes
615                if ( compare & 1 ||
616                     (recompare && b.compareDocumentPosition( a ) ===compare) ) {617
618                     //Choose the first element that is related to our preferred document
619                     if ( a === doc ||contains(preferredDoc, a) ) {620                          return -1;621 }622                     if ( b === doc ||contains(preferredDoc, b) ) {623                          return 1;624 }625
626                     //Maintain original order
627                     return sortInput ?
628                          ( indexOf.call( sortInput, a ) -indexOf.call( sortInput, b ) ) :629                          0;630 }631
632                return compare & 4 ? -1 : 1;633 }634
635           //不能直接比较,sort上不存在这个方法
636           return a.compareDocumentPosition ? -1 : 1;637 } :638      function( a, b ) {639           varcur,640                i = 0,641                aup =a.parentNode,642                bup =b.parentNode,643                ap =[ a ],644                bp =[ b ];645
646           //Exit early if the nodes are identical
647           if ( a ===b ) {648                hasDuplicate = true;649                return 0;650
651           //Parentless nodes are either documents or disconnected
652           } else if ( !aup || !bup ) {653                return a === doc ? -1:654                     b === doc ? 1:655                     aup ? -1:656                     bup ? 1:657                     0;658
659           //If the nodes are siblings, we can do a quick check
660           } else if ( aup ===bup ) {661                returnsiblingCheck( a, b );662 }663
664           //Otherwise we need full lists of their ancestors for comparison
665           cur =a;666           while ( (cur =cur.parentNode) ) {667 ap.unshift( cur );668 }669           cur =b;670           while ( (cur =cur.parentNode) ) {671 bp.unshift( cur );672 }673
674           //Walk down the tree looking for a discrepancy
675           while ( ap[i] ===bp[i] ) {676                i++;677 }678
679           return i ?
680                //Do a sibling check if the nodes have a common ancestor
681 siblingCheck( ap[i], bp[i] ) :682
683                //Otherwise nodes in our document sort first
684                ap[i] === preferredDoc ? -1:685                bp[i] === preferredDoc ? 1:686                0;687 };688
689      returndocument;690 };691 //筛选匹配的elements
692 Sizzle.matches = function( expr, elements ) {693      return Sizzle( expr, null, null, elements );694 };695
696 Sizzle.matchesSelector = function( elem, expr ) {697      //Set document vars if needed
698      if ( ( elem.ownerDocument || elem ) !==document ) {699 setDocument( elem );700 }701
702      //确保属性选择器有引号(querySelectorAll需要有引号)
703      expr = expr.replace( rattributeQuotes, "='$1']");704
705      //rbuggyQSA中肯定存在:focus,所以不需要检查是否存在
706      if ( support.matchesSelector && documentIsHTML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) {707           try{708                var ret =matches.call( elem, expr );709
710                //ie9和文档分离的node返回false
711                //同样在ie9中文档片段也是分离的节点(createElement('div')这样创建的节点有document属性.document.nodeType为11)
712                if ( ret || support.disconnectedMatch || elem.document && elem.document.nodeType !== 11) {713                     returnret;714 }715           } catch(e) {}716 }717
718      return Sizzle( expr, document, null, [elem] ).length > 0;719 };720
721 Sizzle.contains = function( context, elem ) {722      //Set document vars if needed
723      if ( ( context.ownerDocument || context ) !==document ) {724 setDocument( context );725 }726      returncontains( context, elem );727 };728
729 Sizzle.attr = function( elem, name ) {730      varval;731
732      //Set document vars if needed
733      if ( ( elem.ownerDocument || elem ) !==document ) {734 setDocument( elem );735 }736
737      if( documentIsHTML ) {738           name =name.toLowerCase();739 }740      if ( (val =Expr.attrHandle[ name ]) ) {741           returnval( elem );742 }743      if ( !documentIsHTML ||support.attributes ) {744           returnelem.getAttribute( name );745 }746      return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ?
747 name :748           val && val.specified ? val.value : null;749 };750
751 Sizzle.error = function( msg ) {752      throw new Error( "Syntax error, unrecognized expression: " +msg );753 };754
755 //排序并去重
756 Sizzle.uniqueSort = function( results ) {757      varelem,758           duplicates =[],759           j = 0,760           i = 0;761
762      //Unless we *know* we can detect duplicates, assume their presence
763      //除非我们知道我们可以检测到重复的,假设他们的存在
764      hasDuplicate = !support.detectDuplicates;765      //Compensate for sort limitations
766      recompare = !support.sortDetached;767      sortInput = !support.sortStable && results.slice( 0);768 results.sort( sortOrder );769
770      if( hasDuplicate ) {771           while ( (elem = results[i++]) ) {772                if ( elem ===results[ i ] ) {773                     j =duplicates.push( i );774 }775 }776           while ( j--) {777                results.splice( duplicates[ j ], 1);778 }779 }780
781      returnresults;782 };783
784 //兄弟节点排序
785
786 functionsiblingCheck( a, b ) {787      var cur = b &&a,788           diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex ||MAX_NEGATIVE );789
790      //Use IE sourceIndex if available on both nodes
791      if( diff ) {792           returndiff;793 }794
795      //Check if b follows a
796      if( cur ) {797           while ( (cur =cur.nextSibling) ) {798                if ( cur ===b ) {799                     return -1;800 }801 }802 }803
804      return a ? 1 : -1;805 }806
807 //创建一个伪类的过滤函数,此方法是根据表单元素的type值生成
808 //比如:radio, :text, :checkbox, :file, :image等自定义伪类
809 functioncreateInputPseudo( type ) {810      return function( elem ) {811           var name =elem.nodeName.toLowerCase();812           return name === "input" && elem.type ===type;813 };814 }815
816 //创建一个伪类的过滤函数,此方法是根据表单元素的type值或标签类型生成
817 //如果:button, :submit自定义伪类
818 functioncreateButtonPseudo( type ) {819      return function( elem ) {820           var name =elem.nodeName.toLowerCase();821           return (name === "input" || name === "button") && elem.type ===type;822 };823 }824
825 //用于创建位置伪类的过滤函数,它们是模拟从左向右的顺序进行选择,
826 //匹配到它时的结果集的位置来挑选元素的
827 //比如:odd,:even, :eq, :gt, :lt, :first, :last
828 functioncreatePositionalPseudo( fn ) {829      return markFunction(function( argument ) {830           argument = +argument;831           return markFunction(function( seed, matches ) {832                varj,833                     matchIndexes =fn( [], seed.length, argument ),834                     i =matchIndexes.length;835
836                //Match elements found at the specified indexes
837                while ( i--) {838                     if ( seed[ (j =matchIndexes[i]) ] ) {839                          seed[j] = !(matches[j] = seed[j]);//这里的j是数组索引值,matches数组中可能会出现undefinde值 如:var arr = []; arr[10] = 10;这里前9项会是undefined
840 }841 }842 });843 });844 }845
846 //工具函数用于获取给定节点的text
847 getText = Sizzle.getText = function( elem ) {848      varnode,849           ret = "",850           i = 0,851           nodeType =elem.nodeType;852
853      if ( !nodeType ) {854           //If no nodeType, this is expected to be an array
855           for ( ; (node = elem[i]); i++) {856                //Do not traverse comment nodes
857                ret +=getText( node );858 }859      } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11) {860           //Use textContent for elements
861           //innerText usage removed for consistency of new lines (see #11153)
862           if ( typeof elem.textContent === "string") {863                returnelem.textContent;864           } else{865                //Traverse its children
866                for ( elem = elem.firstChild; elem; elem =elem.nextSibling ) {867                     ret +=getText( elem );868 }869 }870      } else if ( nodeType === 3 || nodeType === 4) {871           returnelem.nodeValue;872 }873      //Do not include comment or processing instruction nodes
874
875      returnret;876 };877
878 Expr = Sizzle.selectors ={879
880      //Can be adjusted by the user
881      cacheLength: 50,882
883 createPseudo: markFunction,884
885 match: matchExpr,886
887      find: {},//查找元素
888
889 relative: {890           ">": { dir: "parentNode", first: true},891           " ": { dir: "parentNode"},892           "+": { dir: "previousSibling", first: true},893           "~": { dir: "previousSibling"}894 },895
896      preFilter: {//正则出来的数组不能直接用需要过滤下如:attr正则出来的数组引号还有~=
897           "ATTR": function( match ) {898                match[1] = match[1].replace( runescape, funescape );899
900                //不管有没有引号都把value值移到match[3]
901                //有引号["[name="username"]", "name", "=", """, "username", undefined]
902                //无引号["[name=username]", "name", "=", undefined, undefined, "username"]
903                match[3] = ( match[4] || match[5] || "").replace( runescape, funescape );904
905                if ( match[2] === "~=" ) {//匹配属性值必须有空格分隔(这个选择器测试属性值中的每个单词字符串,其中“word”是一个由空白分隔的字符串定义的)
906                     match[3] = " " + match[3] + " ";907 }908
909                return match.slice( 0, 4);910 },911
912           "CHILD": function( match ) {913                /*matches from matchExpr["CHILD"]914 1 type (only|nth|...)915 2 what (child|of-type)916 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)917 4 xn-component of xn+y argument ([+-]?\d*n|)918 5 sign of xn-component919 6 x of xn-component920 7 sign of y-component921 8 y of y-component922                */
923                //[":nth-child(2n + 1)", "nth", "child", "2n + 1", "2n", "", "2", "+", "1"]
924                //[":nth-last-child(1)", "nth-last", "child", "1", "", undefined, undefined, "", "1"]
925                //[":nth-child(even)", "nth", "child", "even", undefined, undefined, undefined, undefined, undefined]
926                match[1] = match[1].toLowerCase();927                //当选择器中有nth时
928                if ( match[1].slice( 0, 3 ) === "nth") {929                     //nth-* requires argument
930                     //当选择器中有nth时小括号内没有值说明不是合法的CHILD选择器
931                     if ( !match[3] ) {932                          Sizzle.error( match[0] );933 }934
935                     //numeric x and y parameters for Expr.filter.CHILD
936                     //remember that false/true cast respectively to 0/1
937                     //处理(xn + y)
938                     //true和false分别转换为1和0,  true和false参与运算时会自动转换为1和0
939                     match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd") );940                     match[5] = +( ( match[7] + match[8] ) || match[3] === "odd");941
942                //other types prohibit arguments
943                //如果不是nth类型但是match[3]有值说明不是合法的CHILD选择器
944                } else if ( match[3] ) {945                     Sizzle.error( match[0] );946 }947
948                returnmatch;949 },950
951           "PSEUDO": function( match ) {952                varexcess,953                     unquoted = !match[5] && match[2];954
955                //如果是孩子伪类不处理
956                if ( matchExpr["CHILD"].test( match[0] ) ) {957                     return null;958 }959                //[":has(input[name=username])", "has", "input[name=username]", undefined, undefined, "input[name=username]", "name", "=", undefined, undefined, "username"]
960                //Accept quoted arguments as-is
961                //如果小括号内有引号 如:[":eq("2")", "eq", ""2"", """, "2", undefined, undefined, undefined, undefined, undefined, undefined]
962                if ( match[4] ) {963                     match[2] = match[4];964
965                //Strip excess characters from unquoted arguments
966                //不知道什么伪类会走这里
967                } else if ( unquoted && rpseudo.test( unquoted ) &&
968                     //Get excess from tokenize (recursively)
969                     (excess = tokenize( unquoted, true )) &&
970                     //advance to the next closing parenthesis
971                     (excess = unquoted.indexOf( ")", unquoted.length - excess ) -unquoted.length) ) {972
973                     //excess is a negative index
974                     match[0] = match[0].slice( 0, excess );975                     match[2] = unquoted.slice( 0, excess );976 }977
978                //返回捕获中仅仅需要的过滤方法(type和argument)
979                return match.slice( 0, 3);980 }981 },982
983      filter: {//过滤函数(判断各节点是否符合条件)
984
985           "TAG": function( nodeName ) {986                if ( nodeName === "*") {987                     return function() { return true; };988 }989
990                nodeName =nodeName.replace( runescape, funescape ).toLowerCase();991                return function( elem ) {992                     return elem.nodeName && elem.nodeName.toLowerCase() ===nodeName;993 };994 },995
996           "CLASS": function( className ) {997                var pattern = classCache[ className + " "];998
999                return pattern ||
1000                     (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
1001                     classCache( className, function( elem ) {1002                          return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "");1003 });1004 },1005
1006           "ATTR": function( name, operator, check ) {1007                return function( elem ) {1008                     var result =Sizzle.attr( elem, name );1009
1010                     if ( result == null) {1011                          return operator === "!=";1012 }1013                     if ( !operator ) {1014                          return true;1015 }1016
1017                     result += "";1018
1019                     return operator === "=" ? result ===check :1020                          operator === "!=" ? result !==check :1021                          operator === "^=" ? check && result.indexOf( check ) === 0:1022                          operator === "*=" ? check && result.indexOf( check ) > -1:1023                          operator === "$=" ? check && result.slice( -check.length ) ===check :1024                          operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1:1025                          operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-":1026                          false;1027 };1028 },1029
1030           //:nth-child(2)
1031           //["nth", "child", "2", 0, 2, undefined, "", "2"]
1032           "CHILD": function( type, what, argument, first, last ) {1033                var simple = type.slice( 0, 3 ) !== "nth",     //是否有nth
1034                     forward = type.slice( -4 ) !== "last",//是否正向查找
1035                     ofType = what === "of-type";     //是否有of-type
1036
1037                return first === 1 && last === 0 ?
1038
1039                     //Shortcut for :nth-*(n)
1040                     function( elem ) {1041                          return !!elem.parentNode;1042 } :1043
1044                     function( elem, context, xml ) {1045                          varcache, outerCache, node, diff, nodeIndex, start,1046                               dir = simple !== forward ? "nextSibling" : "previousSibling",1047                               parent =elem.parentNode,1048                               name = ofType &&elem.nodeName.toLowerCase(),1049                               useCache = !xml && !ofType;1050
1051                          if( parent ) {1052
1053                               //:(first|last|only)-(child|of-type)
1054                               if( simple ) {1055                                    while( dir ) {1056                                         node =elem;1057                                         while ( (node =node[ dir ]) ) {1058                                              //如果不是of-type, first如果前面还有元素返回false, last如果后面还有元素返回false
1059                                              //如果是of-type, name值全等返回false
1060                                              if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) {1061                                                   return false;1062 }1063 }1064                                         //Reverse direction for :only-* (if we haven't yet done so)
1065                                         //如果type为only时,
1066                                         start = dir = type === "only" && !start && "nextSibling";1067 }1068                                    return true;1069 }1070
1071                               start = [ forward ?parent.firstChild : parent.lastChild ];1072
1073                               //non-xml :nth-child(...) stores cache data on `parent`
1074                               if ( forward &&useCache ) {1075                                    //Seek `elem` from a previously-cached index
1076                                    outerCache = parent[ expando ] || (parent[ expando ] ={});1077                                    cache = outerCache[ type ] ||[];1078                                    nodeIndex = cache[0] === dirruns && cache[1];1079                                    diff = cache[0] === dirruns && cache[2];1080                                    node = nodeIndex &&parent.childNodes[ nodeIndex ];1081
1082                                    while ( (node = ++nodeIndex && node && node[ dir ] ||
1083
1084                                         //Fallback to seeking `elem` from the start
1085                                         //第一次执行while循环时走这里(确切的说应该是第一个遍历childNodes节点时)如果start内没有节点则循环一次找到节点,这也是diff = nodeIndex = 0的原因
1086                                         (diff = nodeIndex = 0) ||start.pop()) ) {1087
1088                                         //When found, cache indexes on `parent` and break
1089                                         //当获取到的element,把这个element的索引值缓存在父节点[dirruns, 节点索引值, 真正的节点(不是文本或注释节点)]
1090                                         if ( node.nodeType === 1 && ++diff && node ===elem ) {1091                                              outerCache[ type ] =[ dirruns, nodeIndex, diff ];1092                                              break;1093 }1094 }1095
1096                               //Use previously-cached element index if available
1097                               } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] ===dirruns ) {1098                                    diff = cache[1];1099
1100                               //如果是xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
1101                               } else{1102                                    //Use the same loop as above to seek `elem` from the start
1103                                    while ( (node = ++nodeIndex && node && node[ dir ] ||
1104                                         (diff = nodeIndex = 0) ||start.pop()) ) {1105
1106                                         if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {1107                                              //Cache the index of each encountered element
1108                                              if( useCache ) {1109                                                   (node[ expando ] || (node[ expando ] = {}))[ type ] =[ dirruns, diff ];1110 }1111
1112                                              if ( node ===elem ) {1113                                                   break;1114 }1115 }1116 }1117 }1118
1119                               //Incorporate the offset, then check against cycle size
1120                               diff -=last;1121                               return diff === first || ( diff % first === 0 && diff / first >= 0);1122 }1123 };1124 },1125
1126           "PSEUDO": function( pseudo, argument ) {1127
1128                //伪类names值不区分大小写(不对)
1129                //优先大小写敏感的定制伪类
1130                //setFilters继承了pseudos
1131                varargs,1132                     fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
1133                          Sizzle.error( "unsupported pseudo: " +pseudo );1134                //上面的fn才是真正的筛选函数(有sizzle自己定义的,用户也可以自定义)
1135
1136
1137                //用户可能用createPseuso(需要创建一个过滤函数做为参数)自定义一个伪类, 就像sizzle创建的一样
1138                if( fn[ expando ] ) {1139                     returnfn( argument );1140 }1141
1142                //保持旧的签名支持
1143                if ( fn.length > 1) {1144                     args = [ pseudo, pseudo, "", argument ];1145                     return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
1146                          markFunction(function( seed, matches ) {1147                               varidx,1148                                    matched =fn( seed, argument ),1149                                    i =matched.length;1150                               while ( i--) {1151                                    idx =indexOf.call( seed, matched[i] );1152                                    seed[ idx ] = !( matches[ idx ] =matched[i] );1153 }1154 }) :1155                          function( elem ) {1156                               return fn( elem, 0, args );1157 };1158 }1159
1160                returnfn;1161 }1162 },1163
1164 pseudos: {1165           //Potentially complex pseudos
1166           "not": markFunction(function( selector ) {1167                //Trim the selector passed to compile
1168                //to avoid treating leading and trailing
1169                //spaces as combinators
1170                var input =[],1171                     results =[],1172                     matcher = compile( selector.replace( rtrim, "$1") );1173
1174                return matcher[ expando ] ?
1175                     markFunction(function( seed, matches, context, xml ) {1176                          varelem,1177                               unmatched = matcher( seed, null, xml, [] ),1178                               i =seed.length;1179
1180                          //Match elements unmatched by `matcher`
1181                          while ( i--) {1182                               if ( (elem =unmatched[i]) ) {1183                                    seed[i] = !(matches[i] =elem);1184 }1185 }1186 }) :1187                     function( elem, context, xml ) {1188                          input[0] =elem;1189                          matcher( input, null, xml, results );1190                          return !results.pop();1191 };1192 }),1193
1194           "has": markFunction(function( selector ) {1195                return function( elem ) {1196                     return Sizzle( selector, elem ).length > 0;1197 };1198 }),1199
1200           "contains": markFunction(function( text ) {1201                return function( elem ) {1202                     return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;1203 };1204 }),1205
1206           //"Whether an element is represented by a :lang() selector
1207           //is based solely on the element's language value
1208           //being equal to the identifier C,
1209           //or beginning with the identifier C immediately followed by "-".
1210           //The matching of C against the element's language value is performed case-insensitively.
1211           //The identifier C does not have to be a valid language name."
1212           //http://www.w3.org/TR/selectors/#lang-pseudo
1213           "lang": markFunction( function( lang ) {1214                //lang value must be a valid identifier
1215                if ( !ridentifier.test(lang || "") ) {1216                     Sizzle.error( "unsupported lang: " +lang );1217 }1218                lang =lang.replace( runescape, funescape ).toLowerCase();1219                return function( elem ) {1220                     varelemLang;1221                     do{1222                          if ( (elemLang = documentIsHTML ?
1223 elem.lang :1224                               elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {1225
1226                               elemLang =elemLang.toLowerCase();1227                               return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;1228 }1229                     } while ( (elem = elem.parentNode) && elem.nodeType === 1);1230                     return false;1231 };1232 }),1233
1234           //Miscellaneous
1235           "target": function( elem ) {1236                var hash = window.location &&window.location.hash;1237                return hash && hash.slice( 1 ) ===elem.id;1238 },1239
1240           "root": function( elem ) {1241                return elem ===docElem;1242 },1243
1244           "focus": function( elem ) {1245                return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);1246 },1247
1248           //Boolean properties
1249           "enabled": function( elem ) {1250                return elem.disabled === false;1251 },1252
1253           "disabled": function( elem ) {1254                return elem.disabled === true;1255 },1256
1257           "checked": function( elem ) {1258                //In CSS3, :checked should return both checked and selected elements
1259                //http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1260                var nodeName =elem.nodeName.toLowerCase();1261                return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);1262 },1263
1264           "selected": function( elem ) {1265                //Accessing this property makes selected-by-default
1266                //options in Safari work properly
1267                if( elem.parentNode ) {1268 elem.parentNode.selectedIndex;1269 }1270
1271                return elem.selected === true;1272 },1273
1274           //Contents
1275           "empty": function( elem ) {1276                //http://www.w3.org/TR/selectors/#empty-pseudo
1277                //:empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
1278                //not comment, processing instructions, or others
1279                //Thanks to Diego Perini for the nodeName shortcut
1280                //Greater than "@" means alpha characters (specifically not starting with "#" or "?")
1281                for ( elem = elem.firstChild; elem; elem =elem.nextSibling ) {1282                     if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4) {1283                          return false;1284 }1285 }1286                return true;1287 },1288
1289           "parent": function( elem ) {1290                return !Expr.pseudos["empty"]( elem );1291 },1292
1293           //Element/input types
1294           "header": function( elem ) {1295                returnrheader.test( elem.nodeName );1296 },1297
1298           "input": function( elem ) {1299                returnrinputs.test( elem.nodeName );1300 },1301
1302           "button": function( elem ) {1303                var name =elem.nodeName.toLowerCase();1304                return name === "input" && elem.type === "button" || name === "button";1305 },1306
1307           "text": function( elem ) {1308                varattr;1309                //IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
1310                //use getAttribute instead to test this case
1311                return elem.nodeName.toLowerCase() === "input" &&
1312                     elem.type === "text" &&
1313                     ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() ===elem.type );1314 },1315
1316           //Position-in-collection
1317           "first": createPositionalPseudo(function() {1318                return [ 0];1319 }),1320
1321           "last": createPositionalPseudo(function( matchIndexes, length ) {1322                return [ length - 1];1323 }),1324
1325           "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {1326                return [ argument < 0 ? argument +length : argument ];1327 }),1328
1329           "even": createPositionalPseudo(function( matchIndexes, length ) {1330                var i = 0;1331                for ( ; i < length; i += 2) {1332 matchIndexes.push( i );1333 }1334                returnmatchIndexes;1335 }),1336
1337           "odd": createPositionalPseudo(function( matchIndexes, length ) {1338                var i = 1;1339                for ( ; i < length; i += 2) {1340 matchIndexes.push( i );1341 }1342                returnmatchIndexes;1343 }),1344
1345           "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {1346                var i = argument < 0 ? argument +length : argument;1347                for ( ; --i >= 0; ) {1348 matchIndexes.push( i );1349 }1350                returnmatchIndexes;1351 }),1352
1353           "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {1354                var i = argument < 0 ? argument +length : argument;1355                for ( ; ++i <length; ) {1356 matchIndexes.push( i );1357 }1358                returnmatchIndexes;1359 })1360 }1361 };1362
1363 //添加input和button伪类
1364 for ( i in { radio: true, checkbox: true, file: true, password: true, image: true} ) {1365      Expr.pseudos[ i ] =createInputPseudo( i );1366 }1367 for ( i in { submit: true, reset: true} ) {1368      Expr.pseudos[ i ] =createButtonPseudo( i );1369 }1370
1371 //把selector解析成一个个独立的块
1372 functiontokenize( selector, parseOnly ) {1373      varmatched, match, tokens, type,1374 soFar, groups, preFilters,1375           cached = tokenCache[ selector + " "];1376
1377      if( cached ) {1378           return parseOnly ? 0 : cached.slice( 0);1379 }1380
1381      soFar =selector;1382      groups =[];1383      preFilters = Expr.preFilter;//正则出来的数组不能直接用需要过滤下如:attr正则出来的数组引号还有~=
1384
1385      while( soFar ) {1386
1387           //第一次运行或者并联选择器逗号
1388           if ( !matched || (match =rcomma.exec( soFar )) ) {1389                if( match ) {1390                     //Don't consume trailing commas as valid
1391                     soFar = soFar.slice( match[0].length ) ||soFar;1392 }1393                groups.push( tokens =[] );1394 }1395
1396           matched = false;1397
1398           //关系选择器
1399           if ( (match =rcombinators.exec( soFar )) ) {1400                matched =match.shift();1401 tokens.push( {1402 value: matched,1403                     type: match[0].replace( rtrim, " ")1404 } );1405                soFar =soFar.slice( matched.length );1406 }1407
1408           //过滤器
1409           for ( type inExpr.filter ) {1410                //这里如果已匹配上是不是退出for等循环效率高点????tokenize函数要不要写下(把rcomma、rcombinators、其它选择器写到一个for循环)
1411                if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
1412                     (match =preFilters[ type ]( match ))) ) {1413                     matched = match.shift();//真正的选择器
1414 tokens.push( {1415                          value: matched,//真正的选择器
1416                          type: type,//选择器类型
1417                          matches: match//选择器的各个部分如:attr ["[name="username"]", "name", "=", "username"]
1418 } );1419                     soFar = soFar.slice( matched.length );//截取当前匹配的选择器
1420 }1421 }1422
1423           if ( !matched ) {1424                break;1425 }1426 }1427
1428      //如果我们仅仅解析返回无效的长度,否则返回解析完的选择器对象或者抛出错误
1429      return parseOnly ?
1430 soFar.length :1431           soFar ?
1432 Sizzle.error( selector ) :1433                //Cache the tokens
1434                //缓存解析完的选择器
1435                tokenCache( selector, groups ).slice( 0 );//拷贝缓存中的,因为是引用类型如果直接使用会改变缓存中的值
1436 }1437
1438 functiontoSelector( tokens ) {1439      var i = 0,1440           len =tokens.length,1441           selector = "";1442      for ( ; i < len; i++) {1443           selector +=tokens[i].value;1444 }1445      returnselector;1446 }1447
1448 //根据关系选择器检查
1449 functionaddCombinator( matcher, combinator, base ) {1450      var dir =combinator.dir,1451           checkNonElements = base && dir === "parentNode",1452           doneName = done++;//第几个关系选择器
1453 console.log('遇到第' + doneName + '个关系选择器:');1454      return combinator.first ?
1455           //Check against closest ancestor/preceding element
1456           //检查最靠近的祖先元素
1457           function( elem, context, xml ) {1458                while ( (elem =elem[ dir ]) ) {1459                     if ( elem.nodeType === 1 ||checkNonElements ) {1460                          returnmatcher( elem, context, xml );1461 }1462 }1463 } :1464
1465           //Check against all ancestor/preceding elements
1466           //检查最靠近的祖先元素或兄弟元素(概据>、~、+还有空格检查)
1467           function( elem, context, xml ) {1468                vardata, cache, outerCache,1469                     dirkey = dirruns + " " +doneName;1470
1471                //我们不可以在xml节点上设置任意数据,所以它们不会从dir缓存中受益
1472                if( xml ) {1473                     while ( (elem =elem[ dir ]) ) {1474                          if ( elem.nodeType === 1 ||checkNonElements ) {1475                               if( matcher( elem, context, xml ) ) {1476                                    return true;1477 }1478 }1479 }1480                } else{1481                     while ( (elem =elem[ dir ]) ) {1482                          if ( elem.nodeType === 1 ||checkNonElements ) {1483                               outerCache = elem[ expando ] || (elem[ expando ] ={});1484                               //如果有缓存且符合下列条件则不用再次调用matcher函数
1485                               if ( (cache = outerCache[ dir ]) && cache[0] ===dirkey ) {1486                                    if ( (data = cache[1]) === true || data ===cachedruns ) {1487                                         return data === true;1488 }1489                               } else{1490                                    cache = outerCache[ dir ] =[ dirkey ];1491                                    cache[1] = matcher( elem, context, xml ) || cachedruns;//cachedruns//正在匹配第几个元素
1492                                    if ( cache[1] === true) {1493                                         return true;1494 }1495 }1496 }1497 }1498 }1499 };1500 }1501
1502 functionelementMatcher( matchers ) {1503      return matchers.length > 1 ?
1504           function( elem, context, xml ) {1505               //这里代码改为下面代码方便调试
1506              //var i = matchers.length;
1507 //while ( i-- ) {
1508 //if ( !matchers[i]( elem, context, xml ) ) {
1509 //return false;
1510 //}
1511 //}
1512                var i =matchers.length;1513                var matcher = null;1514                while ( i--) {1515                     matcher =matchers[i];1516                     //console.log(matcher)
1517                     if ( !matcher( elem, context, xml ) ) {1518                          return false;1519 }1520 }1521                return true;1522 } :1523           matchers[0];1524 }1525
1526 functioncondense( unmatched, map, filter, context, xml ) {1527      varelem,1528           newUnmatched =[],1529           i = 0,1530           len =unmatched.length,1531           mapped = map != null;1532
1533      for ( ; i < len; i++) {1534           if ( (elem =unmatched[i]) ) {1535                if ( !filter ||filter( elem, context, xml ) ) {1536 newUnmatched.push( elem );1537                     if( mapped ) {1538 map.push( i );1539 }1540 }1541 }1542 }1543
1544      returnnewUnmatched;1545 }1546
1547 //如:'\#test a input[name="username"]:last[type="text"] [value*=2]'(不建议写这样的选择器)
1548 //preFilter位置伪类之前的所有选择器过滤函数 如:'a input[name="username"]'选择器的匹配函数
1549 //selector 位置伪类之前的所有选择器 如:'a input[name="username"]'
1550 //matcher 位置伪类匹配函数
1551 //postFilter如果位置伪类后面还有选择器, 且关系选择器之前(如果有的话) 此选择器的匹配函数
1552 //postFinder如果位置伪类后面还有关系选择器,第一个关系选择器以后的匹配函数
1553 //postSelector如果位置伪类后面还有关系选择器,第一个关系选择器以后的选择器 如:' [value*=2]'
1554 functionsetMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {1555      if ( postFilter && !postFilter[ expando ] ) {1556           postFilter =setMatcher( postFilter );1557 }1558      if ( postFinder && !postFinder[ expando ] ) {1559           postFinder =setMatcher( postFinder, postSelector );1560 }1561      return markFunction(function( seed, results, context, xml ) {//得到位置伪类之前的选择器的元素后在筛选出符合条件伪类选择器的元素
1562           vartemp, i, elem,1563                preMap =[],1564                postMap =[],1565                preexisting =results.length,1566
1567                //根据把位置伪类前面的选择器查找出元素然后再筛选位置伪类和它后面的选择器
1568                elems = seed || multipleContexts( selector || "*", context.nodeType ?[ context ] : context, [] ),1569
1570                //Prefilter to get matcher input, preserving a map for seed-results synchronization
1571                matcherIn = preFilter && ( seed || !selector ) ?
1572 condense( elems, preMap, preFilter, context, xml ) :1573 elems,1574
1575                matcherOut = matcher ?
1576                     //If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
1577                     //如果postFinder存在,或者筛选的种子集存在,再或者筛选的种子集不存在但是postFilter存在或results中有值
1578                     postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
1579
1580                          //...intermediate processing is necessary必须中间处理
1581 [] :1582
1583                          //...otherwise use results directly否则立即用resluts
1584 results :1585 matcherIn;1586
1587           //Find primary matches
1588           if( matcher ) {1589 matcher( matcherIn, matcherOut, context, xml );1590 }1591
1592           //Apply postFilter
1593           if( postFilter ) {1594                temp =condense( matcherOut, postMap );1595 postFilter( temp, [], context, xml );1596
1597                //Un-match failing elements by moving them back to matcherIn
1598                //把没有匹配失败的元素放到matcherIn中
1599                i =temp.length;1600                while ( i--) {1601                     if ( (elem =temp[i]) ) {1602                          matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] =elem);1603 }1604 }1605 }1606
1607           if( seed ) {1608                if ( postFinder ||preFilter ) {1609                     if( postFinder ) {1610                          //Get the final matcherOut by condensing this intermediate into postFinder contexts
1611                          temp =[];1612                          i =matcherOut.length;1613                          while ( i--) {1614                               if ( (elem =matcherOut[i]) ) {1615                                    //Restore matcherIn since elem is not yet a final match
1616                                    temp.push( (matcherIn[i] =elem) );1617 }1618 }1619                          postFinder( null, (matcherOut =[]), temp, xml );1620 }1621
1622                     //Move matched elements from seed to results to keep them synchronized
1623                     i =matcherOut.length;1624                     while ( i--) {1625                          if ( (elem = matcherOut[i]) &&
1626                               (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1) {1627
1628                               seed[temp] = !(results[temp] =elem);1629 }1630 }1631 }1632
1633           //Add elements to results, through postFinder if defined
1634           } else{1635                matcherOut =condense(1636                     matcherOut === results ?
1637 matcherOut.splice( preexisting, matcherOut.length ) :1638 matcherOut1639 );1640                if( postFinder ) {1641                     postFinder( null, results, matcherOut, xml );1642                } else{1643 push.apply( results, matcherOut );1644 }1645 }1646 });1647 }1648
1649 functionmatcherFromTokens( tokens ) {1650      varcheckContext, matcher, j,1651           len =tokens.length,1652           leadingRelative = Expr.relative[ tokens[0].type ],1653           implicitRelative = leadingRelative || Expr.relative[" "],1654           i = leadingRelative ? 1 : 0,1655
1656           //确保这些元素可以在context中找到
1657           matchContext = addCombinator( function( elem ) {1658                return elem ===checkContext;1659           }, implicitRelative, true),1660           matchAnyContext = addCombinator( function( elem ) {1661                return indexOf.call( checkContext, elem ) > -1;1662           }, implicitRelative, true),1663           //这里用来确定元素在哪个context
1664           matchers = [ function( elem, context, xml ) {1665                return ( !leadingRelative && ( xml || context !== outermostContext ) ) ||(1666                     (checkContext = context).nodeType ?
1667 matchContext( elem, context, xml ) :1668 matchAnyContext( elem, context, xml ) );1669 } ];1670
1671      for ( ; i < len; i++) {1672           if ( (matcher =Expr.relative[ tokens[i].type ]) ) {1673                //当遇到关系选择器时elementMatcher函数将matchers数组中的函数生成一个函数(elementMatcher利用了闭包所以matchers一直存在内存中)
1674                matchers =[ addCombinator(elementMatcher( matchers ), matcher) ];1675           } else{1676                matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );//apply方式调用函数, tokens[i].matches为参数
1677
1678                //返回一个特殊的位置匹配函数
1679                //伪类会把selector分两部分
1680                if( matcher[ expando ] ) {1681                     //发现下一个关系操作符(如果有话)并做适当处理
1682                     j = ++i;1683                     for ( ; j < len; j++) {1684                          if ( Expr.relative[ tokens[j].type ] ) {//如果位置伪类后面还有关系选择器还需要筛选
1685                               break;1686 }1687 }1688                     returnsetMatcher(1689                          i > 1 &&elementMatcher( matchers ),1690                          i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1"),1691 matcher,1692                          i < j && matcherFromTokens( tokens.slice( i, j ) ),//如果位置伪类后面还有选择器需要筛选
1693                          j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),//如果位置伪类后面还有关系选择器还需要筛选
1694                          j < len &&toSelector( tokens )1695 );1696 }1697 matchers.push( matcher );1698 }1699 }1700
1701      returnelementMatcher( matchers );1702 }1703
1704 functionmatcherFromGroupMatchers( elementMatchers, setMatchers ) {1705      //A counter to specify which element is currently being matched
1706      //用计数器来指定当前哪个元素正在匹配
1707      var matcherCachedRuns = 0,1708           bySet = setMatchers.length > 0,1709           byElement = elementMatchers.length > 0,1710           superMatcher = function( seed, context, xml, results, expandContext ) {1711                varelem, j, matcher,1712                     setMatched =[],1713                     matchedCount = 0,1714                     i = "0",1715                     unmatched = seed &&[],1716                     outermost = expandContext != null,1717                     contextBackup =outermostContext,1718                     //We must always have either seed elements or context
1719                     //我们必须总是检查seed中的元素或context下的所有元素
1720                     elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode ||context ),1721                     //Use integer dirruns iff this is the outermost matcher
1722                     //用整数dirruns区分这个是最外层的匹配函数
1723                     dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);1724                if( outermost ) {1725                     outermostContext = context !== document &&context;1726                     cachedruns =matcherCachedRuns;1727 }1728
1729                //通过elementMatchers内的所有匹配函数的元素立即添加到results中
1730                //保持变量i是一个字符串,如果一个元素也没有下面的元素匹配数量matchedCount为'00'
1731                for ( ; (elem = elems[i]) != null; i++) {1732                     if ( byElement &&elem ) {1733                          j = 0;1734                          while ( (matcher = elementMatchers[j++]) ) {1735                               if( matcher( elem, context, xml ) ) {1736 results.push( elem );1737                                    break;1738 }1739 }1740                          if( outermost ) {1741                               dirruns =dirrunsUnique;1742                               cachedruns = ++matcherCachedRuns;//正在匹配第几个元素(同时告诉全局的,这里的全局是sizzle内的不是window的)
1743 }1744 }1745
1746                     //Track unmatched elements for set filters
1747                     //跟踪不匹配元素并设置过滤
1748                     if( bySet ) {1749                          //They will have gone through all possible matchers
1750                          //它们已经通过所有的匹配器,如果元素不匹配matchedCount减1
1751                          if ( (elem = !matcher &&elem) ) {1752                               matchedCount--;1753 }1754
1755                          //Lengthen the array for every element, matched or not
1756                          //如果seed中有元素的话,不管是否匹配都要把每个元素放到一个延长数组中
1757                          if( seed ) {1758 unmatched.push( elem );1759 }1760 }1761 }1762
1763                //Apply set filters to unmatched elements
1764                //用设置的过滤器来去除不匹配的元素
1765                matchedCount += i;//i是所有元素数量,matchedCount之前是不匹配的元素数量,所以matchedCount += i就是匹配的数量
1766                if ( bySet && i !==matchedCount ) {1767                     j = 0;1768                     while ( (matcher = setMatchers[j++]) ) {1769                          //这里筛选unmatched内的元素
1770                          matcher( unmatched, setMatched, context, xml );//(这里的setMatched是引用类型,所以函数matcher内给setMatched添加的元素和这里的setMatched一样
1771 }1772
1773                     if( seed ) {1774                          //Reintegrate element matches to eliminate the need for sorting
1775                          if ( matchedCount > 0) {1776                               while ( i--) {1777                                    if ( !(unmatched[i] ||setMatched[i]) ) {1778                                         setMatched[i] =pop.call( results );1779 }1780 }1781 }1782
1783                          //Discard index placeholder values to get only actual matches
1784                          setMatched =condense( setMatched );1785 }1786
1787                     //Add matches to results
1788                     //添加匹配元素到results
1789 push.apply( results, setMatched );1790
1791                     //并联选择器匹配成功后按规定排序
1792                     if ( outermost && !seed && setMatched.length > 0 &&
1793                          ( matchedCount + setMatchers.length ) > 1) {1794
1795 Sizzle.uniqueSort( results );1796 }1797 }1798
1799                //Override manipulation of globals by nested matchers
1800                if( outermost ) {1801                     dirruns =dirrunsUnique;1802                     outermostContext =contextBackup;1803 }1804
1805                returnunmatched;1806 };1807
1808      return bySet ?
1809 markFunction( superMatcher ) :1810 superMatcher;1811 }1812
1813 compile = Sizzle.compile = function( selector, group /*Internal Use Only*/) {1814      vari,1815           setMatchers =[],1816           elementMatchers =[],1817           cached = compilerCache[ selector + " "];1818
1819      if ( !cached ) {1820           //生成一个递归函数用来检查每个元素
1821           if ( !group ) {1822                group =tokenize( selector );1823 }1824           i =group.length;1825         while ( i-- ) {//如果是有并联选择器这里多次等循环
1826                cached =matcherFromTokens( group[i] );1827                if ( cached[ expando ] ) {//说明有位置伪类选择器
1828 setMatchers.push( cached );1829                } else{1830 elementMatchers.push( cached );1831 }1832 }1833
1834           //当是并联选择器时(也就是group数组有多个元素),elementMatchers和setMatchers可能都有值
1835           cached =compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );1836 }1837      returncached;1838 };1839
1840 functionmultipleContexts( selector, contexts, results ) {1841      var i = 0,1842           len =contexts.length;1843      for ( ; i < len; i++) {1844 Sizzle( selector, contexts[i], results );1845 }1846      returnresults;1847 }1848
1849 functionselect( selector, context, results, seed ) {1850      vari, tokens, token, type, find,1851           match = tokenize( selector );//把selector解析成一个个独立的块
1852
1853      if ( !seed ) {1854           //如果不是并联选择器则尽量减少操作
1855           if ( match.length === 1) {1856
1857                //如果第一个是selector是id我们可以设置context快速查找
1858                tokens = match[0] = match[0].slice( 0);1859                if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
1860                          context.nodeType === 9 && documentIsHTML &&
1861                          Expr.relative[ tokens[1].type ] ) {1862
1863                     context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];1864                     if ( !context ) {//如果context这个元素(selector第一个id选择器)都不存在就不用查找了
1865                          returnresults;1866 }1867
1868                     selector = selector.slice( tokens.shift().value.length );//去掉第一个id选择器
1869 }1870
1871                //如果selector中没有有位置伪类从右向左匹配
1872                i = matchExpr["needsContext"].test( selector ) ? 0: tokens.length;1873                while ( i--) {1874                     token =tokens[i];1875
1876                     //如果遇到了关系选择器中止
1877                     if ( Expr.relative[ (type =token.type) ] ) {1878                          break;1879 }1880                     if ( (find =Expr.find[ type ]) ) {1881                          //如果selector第一个选择器是兄弟选择器,则扩大context范围(原因要查找的节点在这里必须是它的父节点或祖先节点)
1882                          if ( (seed =find(1883                               token.matches[0].replace( runescape, funescape ),1884                               rsibling.test( tokens[0].type ) && context.parentNode ||context1885 )) ) {1886
1887                               //如果seed是空的或者tokens中没有值了,就可以return了没有必要查找了
1888                               tokens.splice( i, 1);1889                               selector = seed.length &&toSelector( tokens );1890                               if ( !selector ) {1891 push.apply( results, seed );1892                                    returnresults;1893 }1894
1895                               break;1896 }1897 }1898 }1899 }1900 }1901
1902      //Compile and execute a filtering function
1903      //Provide `match` to avoid retokenization if we modified the selector above
1904      //编译并执行过滤函数
1905      //上面如果修改了selector,match同样修改,我们把match传过去可以避免compile内再次调用tokenize
1906 compile( selector, match )(1907 seed,1908 context,1909           !documentIsHTML,1910 results,1911           rsibling.test( selector )//selector中是否有兄弟关系选择器
1912 );1913      returnresults;1914 }1915
1916 //Deprecated
1917 Expr.pseudos["nth"] = Expr.pseudos["eq"];1918
1919 //Easy API for creating new setFilters
1920 //1921 functionsetFilters() {}1922
1923 setFilters.prototype = Expr.filters =Expr.pseudos;1924 Expr.setFilters = newsetFilters();1925
1926 //Check sort stability
1927 support.sortStable = expando.split("").sort( sortOrder ).join("") ===expando;1928
1929 //初始化默认document
1930 setDocument();1931
1932 //Always assume the presence of duplicates if sort doesn't
1933 //pass them to our comparison function (as in Google Chrome).
1934 [0, 0].sort( sortOrder );1935 support.detectDuplicates =hasDuplicate;1936
1937 //EXPOSE
1938 //sizzle对外公开
1939 if ( typeof define === "function" &&define.amd ) {1940      define(function() { returnSizzle; });1941 } else{1942      window.Sizzle =Sizzle;1943 }1944 //EXPOSE
1945
1946 })( window );

部分参考:http://www.cnblogs.com/rubylouvre/archive/2013/03/05/2943666.html

转载于:https://www.cnblogs.com/daycool/archive/2013/04/15/3023169.html

jquery--选择器sizzle源码分析相关推荐

  1. jQuery-1.9.1源码分析系列(二)jQuery选择器续2——筛选

    前面分析了选择器的结构和几个解析函数,接下来分析jQuery对象的伪类选择器.这里所谓的jQuery对象的伪类选择器就是从已有的jQuery对象(元素集合)中筛选出指定的集合出来. 4.    jQu ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  4. 1号店案例html源码_手把手教一起写jQuery版mini源码,分析jQuery的优势

    适合人群 本文适合0.5~3年的前端开发人员,以及想了解jQuery是什么的小伙伴们. 前言 谈谈个人对jQuery的看法. 如果你是一个五年以上的开发人员,相信你一定认识了解jQuery.这好比你十 ...

  5. jQuery源码分析-10事件处理-Event-事件绑定与删除-bind/unbind+live/die+delegat/unde

    10.4    .bind() .one() 10.4.1  如何使用 .bind( eventType, [eventData], handler(eventObject) )   在匹配的元素上绑 ...

  6. jQuery源码分析-10事件处理-Event-事件绑定与删除-bind/unbind+live/die+delegat/undelegate

    Js代码   作者:nuysoft/高云 QQ:47214707 EMail:nuysoft@gmail.com 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. 后文预告:封装事件对象 便 ...

  7. jQuery源码分析之实例find和filter方法的区别七问

    问题1:jQuery.filter的源码是什么? jQuery.filter = function( expr, elems, not ) {var elem = elems[ 0 ];//如果含有第 ...

  8. jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    Deferred的概念请看第一篇 http://www.cnblogs.com/aaronjs/p/3348569.html ******************构建Deferred对象时候的流程图* ...

  9. jQuery 源码分析第一篇之入口源码

    目前阅读的是jQuery 1.11.3的源码,有参考nuysoft的资料.原来比较喜欢在自己的Evernote上做学习基类,并没有在网上写技术博客的习惯,现在开始学习JS的开源代码,想跟大家多交流,希 ...

最新文章

  1. shell学习笔记 (2)
  2. 准确理解 Precision 准确率, Recall 召回率 , IoU
  3. java socket nio 阻塞_Java NIO实现非阻塞式socket通信
  4. EasyUI中放置Droppable的使用以及实现拖拽排序
  5. vs五子棋c语言代码,五子棋代码C语言版.doc
  6. win2003系统网络安装——基于linux+pxe+dhcp+tftp+samba+ris
  7. android服务应用场景,Android Service的使用介绍
  8. mysql中union与union all的区别
  9. 【C++深度剖析教程2】C++经典问题解析之二 this指针与成员函数
  10. php imagick手册,PHP中使用Imagick实现各种图片效果实例
  11. 计算机组成原理r型指令logisim实现_第一章 计算机体系结构
  12. 清华博士庞天宇90页的PPT分享,如何让AI模型更皮实,更稳定?(精彩直播回放)...
  13. 问答 请问使用OK(raw:jpg)能返回多张图片吗
  14. zabbix安装部署_听说你的学习之路又停留在了“不会安装”
  15. HDOJ水题集合11:桶排序, 折半搜索
  16. 2021-08-13 初识servlet
  17. NZ源码交易平台虚拟交易系统(商家版) 高仿淘码网模板
  18. java编译过程_Java编译运行过程
  19. JWT 帮助类 JWTHelper
  20. 手机总是显示服务器太忙,手机总提示服务器太忙请稍后重试

热门文章

  1. nignx处理Html中SSI技术代码注意事项
  2. 1bit和1byte_1byte等于( )bit_学小易找答案
  3. 腐蚀rust研究台抽奖_中石化青岛安工院专家分享延迟焦化装置的腐蚀风险分析!...
  4. Telnet 爆破 kail_【UZI|SN输给DWG后,AD选手被爆破,弹幕刷了半小时Uzi】英雄联盟S10于10月31日终于正式落幕了_科技资讯...
  5. gin context和官方context_Go语言gin框架从入门到精通(3)
  6. java 找不到构造函数_JAVA找不到符号构造函数
  7. Zabbix 3.0 部署监控 [二]
  8. 互联网晚报 | 9月5日 星期日 | 美菜回应大规模裁员;网易云音乐Q2毛利率首次转正;美团展示数字人民币新应用...
  9. 互联网日报 | 京东开启最大规模校招;特斯拉西部首个交付中心在蓉投入使用;嫦娥五号上升器点火起飞...
  10. 2020年上半年家电市场报告