Today, I started introducing jquery's heart, CSS selector. However, sizzle is so complicated that I find that it cannot be read in a row following John resig's idea. Therefore, the following code is different from jquery's order.
Jquery's code is contained in a huge closure, and sizzle opens up another closure in it. It is completely independent of jquery. jquery calls sizzle through the find method. These variables, especially the regular expression, are used to break down the input strings.
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,done = 0,toString = Object.prototype.toString;
Then let's look at its expression for deep processing, filtering, and simple search:
// @ Author situ Zheng Mei | zookeeper | cheng http://www.cnblogs.com/rubylouvre/ all rights reservedvar expr = sizzle. selectors = {order: ["ID", "name", "tag"], Match: {ID :/#((?: [\ W \ u00c0-\ Uffff _-] | \.) +)/, class :/\.((?: [\ W \ u00c0-\ Uffff _-] | \.) +)/, name:/\ [name = ['"] * (?: [\ W \ u00c0-\ Uffff _-] | \.) +) ['"] * \]/, ATTR:/\ [\ s *((?: [\ W \ u00c0-\ Uffff _-] | \.) +) \ s *(? :( \ S? =) \ S * (['"] *) (. *?) \ 3 |) \ s * \]/, Tag:/^ ((?: [\ W \ u00c0-\ Uffff \ * _-] | \.) +)/, Child:/:( only | nth | last | First)-child (?: \ (Even | odd | [\ DN +-] *) \)? /, POS:/:( nth | EQ | GT | lt | first | last | even | ODD )(?: \ (\ D *)\))? (? = [^-] | $)/, Pseudo :/:((?: [\ W \ u00c0-\ Uffff _-] | \.) + )(?: \ (['"] *) (?: \ ([^ \)] + \) | [^ \ 2 \ (\)] *) +) \ 2 \))? //}, Attrmap: {// some attributes cannot be retrieved directly by their HTML name. You need to use the Javascript attribute name "class": "classname", "": "htmlfor"}, attrhandle: {href: function (ELEM) {return ELEM. getattribute ("href") ;}}, relative: {// adjacent selector "+": function (checkset, part, isxml) {var ispartstr = typeof part = "string", istag = ispartstr &&! /\ W/. Test (Part), ispartstrnottag = ispartstr &&! Istag; If (istag &&! Isxml) {part = part. touppercase () ;}for (VAR I = 0, L = checkset. length, ELEM; I <L; I ++) {If (ELEM = checkset [I]) {While (ELEM = ELEM. previussibling) & ELEM. nodetype! = 1) {} checkset [I] = ispartstrnottag | ELEM & ELEM. nodename = part? ELEM | false: ELEM = part;} If (ispartstrnottag) {sizzle. filter (part, checkset, true) ;}}, // parent-child selector ">": function (checkset, part, isxml) {var ispartstr = typeof part = "string"; if (ispartstr &&! /\ W/. Test (part) {part = isxml? Part: part. touppercase (); For (VAR I = 0, L = checkset. length; I <L; I ++) {var ELEM = checkset [I]; If (ELEM) {var parent = ELEM. parentnode; checkset [I] = parent. nodename = part? Parent: false ;}} else {for (VAR I = 0, L = checkset. length; I <L; I ++) {var ELEM = checkset [I]; If (ELEM) {checkset [I] = ispartstr? ELEM. parentnode: ELEM. parentnode = part;} If (ispartstr) {sizzle. filter (part, checkset, true) ;}}, // descendant selector "": function (checkset, part, isxml) {var donename = Done ++, checkfn = dircheck; If (! Part. Match (/\ W/) {var nodecheck = part = isxml? Part: part. touppercase (); checkfn = dirnodecheck;} checkfn ("parentnode", part, donename, checkset, nodecheck, isxml);}, // brother selector "~ ": Function (checkset, part, isxml) {var donename = Done ++, checkfn = dircheck; If (typeof part =" string "&&! Part. Match (/\ W/) {var nodecheck = part = isxml? Part: part. touppercase (); checkfn = dirnodecheck;} checkfn ("previussibling", part, donename, checkset, nodecheck, isxml) ;}, find :{ ID: function (match, context, isxml) {If (typeof context. getelementbyid! = "Undefined "&&! Isxml) {var M = context. getelementbyid (Match [1]); Return M? [M]: []; // put into the array even if there is only one}, name: function (match, context, isxml) {If (typeof context. getelementsbyname! = "Undefined") {var ret = [], Results = context. getelementsbyname (Match [1]); For (VAR I = 0, L = results. length; I <L; I ++) {If (results [I]. getattribute ("name") === match [1]) {ret. push (results [I]);} return ret. length = 0? Null: Ret ;}, Tag: function (match, context) {return context. getelementsbytagname (Match [1]) ;}}, prefilter: {// here, if yes, all returned strings class: function (match, curloop, inplace, result, not, isxml) {match = "" + match [1]. replace (// \/g, "") + ""; if (isxml) {return match;} For (VAR I = 0, ELEM; (ELEM = curloop [I])! = NULL; I ++) {If (ELEM) {// equivalent to hasclassnameif (not ^ (ELEM. classname & ("" + ELEM. classname + ""). indexof (MATCH)> = 0) {If (! Inplace) result. push (ELEM);} else if (inplace) {curloop [I] = false ;}}return false ;}, ID: function (MATCH) {return match [1]. replace (// \/g, "") ;}, Tag: function (match, curloop) {for (VAR I = 0; curloop [I] === false; I ++) {} return curloop [I] & isxml (curloop [I])? Match [1]: Match [1]. touppercase () ;}, child: function (MATCH) {// set Nth (****) the expressions in them are like an + B. If (Match [1] = "nth") {// parse equations like 'even', 'odd', '5 ', '2n ', '3n + 2', '4n-1','-N + 6' var test = /(-?) (\ D *) n ((?: \ + | -)? \ D *)/. exec (Match [2] = "even" & "2n" | match [2] = "odd" & "2n + 1" |! /\ D /. test (Match [2]) & "0n +" + match [2] | match [2]); // calculate the numbers (first) N + (last) including if they are negativematch [2] = (test [1] + (test [2] | 1)-0; match [3] = test [3]-0;} // todo: Move to normal caching systemmatch [0] = Done ++; Return match ;}, ATTR: function (match, curloop, inplace, result, not, isxml) {var name = match [1]. replace (// \/g, ""); If (! Isxml & expr. attrmap [name]) {match [1] = expr. attrmap [name];} If (Match [2] = "~ = ") {Match [4] =" "+ match [4] +" ";}return match ;}, pseudo: function (match, curloop, inplace, result, not) {If (Match [1] === "not") {// If We're re dealing with a complex expression, or a simple oneif (Match [3]. match (chunker ). length> 1 |/^ \ W /. test (Match [3]) {match [3] = sizzle (Match [3], null, null, curloop);} else {var ret = sizzle. filter (Match [3], curloop, inplace, true ^ not); If (! Inplace) {result. push. apply (result, RET);} return false;} else if (expr. match. POS. test (Match [0]) | expr. match. child. test (Match [0]) {return true;} return match;}, POS: function (MATCH) {match. unshift (true); Return match; }}, filters: {// both return the Boolean value enabled: function (ELEM) {// cannot be the hidden domain return ELEM. disabled = false & ELEM. type! = "Hidden";}, Disabled: function (ELEM) {return ELEM. disabled = true;}, checked: function (ELEM) {return ELEM. checked = true;}, selected: function (ELEM) {// accessing this property makes selected-by-default/options in safari work properlyelem. parentnode. selectedindex; return ELEM. selected = true;}, parent: function (ELEM) {// whether it is a parent node (yes, there must be the first child node) return !! ELEM. firstchild;}, empty: function (ELEM) {// whether it is null, and no return at all! ELEM. firstchild;}, has: function (ELEM, I, match) {return !! Sizzle (Match [3], ELEM ). length;}, header: function (ELEM) {// whether it is H1, H2, H3, H4, H5, h6return/h \ D/I. test (ELEM. nodename) ;}, text: function (ELEM) {// text field, which is similar to the following and can be basically classified as the attribute selector return "text" === ELEM. type ;}, radio: function (ELEM) {return "radio" === ELEM. type ;}, checkbox: function (ELEM) {return "checkbox" === ELEM. type ;}, file: function (ELEM) {return "file" === ELEM. type;}, password: function (ELEM) {return "password" === ELEM. type ;}, submit: function (ELEM) {return "Submit" === ELEM. type ;}, image: function (ELEM) {return "image" === ELEM. type ;}, Reset: function (ELEM) {return "reset" === ELEM. type ;}, button: function (ELEM) {return "button" === ELEM. type | ELEM. nodename. touppercase () = "button" ;}, input: function (ELEM) {return/input | select | textarea | button/I. test (ELEM. nodename) ;}}, setfilters: {// subelement filter first: function (El Em, I) {return I = 0;}, last: function (ELEM, I, match, array) {return I = array. length-1 ;}, even: function (ELEM, I) {return I % 2 === 0 ;}, odd: function (ELEM, I) {return I % 2 = 1 ;}, LT: function (ELEM, I, match) {return I <match [3]-0 ;}, GT: function (ELEM, I, match) {return I> match [3]-0 ;}, nth: function (ELEM, I, match) {return match [3]-0 = I;}, EQ: function (ELEM, I, match) {return match [3]-0 = = I ;}, filter: {pseudo: function (ELEM, match, I, array) {var name = match [1], filter = expr. filters [name]; If (filter) {return filter (ELEM, I, match, array);} else if (name = "contains") {return (ELEM. textcontent | ELEM. innertext | ""). indexof (Match [3])> = 0;} else if (name = "not") {var not = match [3]; for (VAR I = 0, L = Not. length; I <L; I ++) {If (not [I] === ELEM) {return Fals E ;}} return true ;}}, child: function (ELEM, match) {var type = match [1], node = ELEM; Switch (type) {Case 'only': Case 'first': While (node = node. previussibling) {If (node. nodetype = 1) return false;} If (type = 'first ') return true; node = ELEM; Case 'La': While (node = node. nextsibling) {If (node. nodetype = 1) return false;} return true; Case 'nth': var first = match [2], last = match [3]; If (FIR St = 1 & last = 0) {return true;} var donename = match [0], parent = ELEM. parentnode; If (parent & (parent. sizcache! = Donename |! ELEM. nodeindex) {var COUNT = 0; For (node = parent. firstchild; node = node. nextsibling) {If (node. nodetype = 1) {node. nodeindex = ++ count; // Add a private property} parent. sizcache = donename;} var diff = ELEM. nodeindex-last; If (first = 0) {return diff = 0; // determine whether it is the first child element} else {return (diff % first = 0 & diff/first> = 0) ;}}, ID: function (ELEM, match) {return ELEM. nodetype = 1 & ELEM. geta Ttribute ("ID") === match;}, Tag: function (ELEM, match) {return (match = "*" & ELEM. nodetype = 1) | ELEM. nodename = match;}, class: function (ELEM, match) {return ("" + (ELEM. classname | ELEM. getattribute ("class") + ""). indexof (MATCH)>-1 ;}, ATTR: function (ELEM, match) {var name = match [1], result = expr. attrhandle [name]? Expr. attrhandle [name] (ELEM): ELEM [name]! = NULL? ELEM [name]: ELEM. getattribute (name), value = Result + "", type = match [2], check = match [4]; return result = NULL? Type = "! = ": TYPE =" = "? Value = check: TYPE = "* = "? Value. indexof (check)> = 0: TYPE = "~ = "? ("" + Value + ""). indexof (check)> = 0 :! Check? Value & result! = False: TYPE = "! = "? Value! = Check: TYPE = "^ = "? Value. indexof (check) = 0: TYPE = "$ = "? Value. substr (value. Length-check. Length) === check: TYPE = "| = "? Value = check | value. substr (0, check. length + 1) = check + "-": false;}, POS: function (ELEM, match, I, array) {var name = match [2], filter = expr. setfilters [name]; If (filter) {return filter (ELEM, I, match, array) ;}}; var origpos = expr. match. pos;
However, it does not completely show the complex mechanism of sizzle. It is working from left to right, processing a string, searching, and then Filtering non-element nodes, then, filter the elements based on their attributes or content or in the order of the parent element, and then to the next string. Then, the search start point is the element node of the last result array. Imagine the Grass-roots look. In many cases, selectors rely on work. element. getelementsbytagname (*) gets all the descendants of one element, so there are a lot of filters in expr. To speed up searching, for example, some browsers have implemented getelementsbyclassname, and jquery also tries to use them.
For (VAR type in expr. match) {// rewrite expr. the Regular Expression in match makes it more rigorous with the assertion of negative zero width. match [type] = Regexp (expr. match [type]. source + /(?! [^ \ [] * \]) (?! [^ \ (] * \)/. Source );}
Next, let's look at the main program and continue to look at its auxiliary methods.
// Convert nodelist htmlcollection to a pure array. If there is a second parameter (the result of the last query), add them to the VaR makearray = function (array, results) in the result set) {array = array. prototype. slice. call (array); If (results) {results. push. apply (results, array); return results;} return array ;}; try {// basically used to test IE, the nodelist htmlcollection of IE does not support converting slice of array into array. prototype. slice. call (document.doc umentelement. childnodes); // In this case, the makearray will be reloaded and elements will be moved into an empty array.} catch (e) {makearray = function (array, Results) {var ret = Results | []; If (tostring. call (array) = "[object array]") {array. prototype. push. apply (Ret, array);} else {If (typeof array. length = "Number") {for (VAR I = 0, L = array. length; I <L; I ++) {ret. push (array [I]) ;}} else {for (VAR I = 0; array [I]; I ++) {ret. push (array [I]) ;}} return ret ;};}