preface

After writing two articles, the idea is to analyze the entire function in the order it runs, so it’s time for the main methods of Tokenize and Sizzle. I will still in accordance with the original way, the first to use these two functions in some of the properties or methods within the first on the front, and then analyzed these two functions, some have seen regular functions or variables including before, I will not repeat lists, students can go to my previous article you don’t know how to check. Sizzle source code although looked for a long time, but the progress is really slow, a lot of things need to break a little bit to see, some compatibility is not very clear, to change the browser to test. OK, complain to here, start the source code.

Global variables and methods used

By global, I mean the scope in the IIFE

variable

  1. tokenCache = createCache tokenizeThe precompiled cache returns the result the next time the same selector is encountered
  2. nonnativeSelectorCache = createCacheThis nativequerySelectorAllUnable to return result of selector string in cache, next time if appear go directlySizzleMethod, no longer go native methods
  3. Expr.prefilterThis thing is due to the imageattribute pesuod childToo many capture groups have been captured and the capture group needs to be processed twice.
  4. Expr.cacheLengthConstant, number, controls the number of caches, the value is 50.

methods

  1. createCacheThe method is to create a cache
function createCache() {
    var keys = [];
    function cache(key, value) {
        // array.prototype. push returns the length of the current Array after push
        if (keys.push(key) > Expr.cacheLength) {
            //Array.prototype.shift returns the value that was shifted out
            delete cache[keys.shift()];
        }
        return ( cache[key + ' '] = value );
    }
    return cache;
}
Copy the code
  1. push
push = arr.push;
slice = arr.slice;
try {
  push.apply(
    (arr = slice.call(preferredDoc.childNodes)),
    preferredDoc.childNodes
  );
  arr[preferredDoc.childNodes.length].nodeType
} catch(e) {
    push = { apply : arr.length ?
        function (target, els) {
            pushNative.apply(target, slice.call(els))
        } :
        function (target, els) {
            var j = traget.length,
                i = 0;
            while((traget[i++] = els[i++])){}
            traget.legth = j - 1; }}}Copy the code
  1. Expr.prefilter
Expr = {
    // ...
    prefilter: {
        // The argument is the array to which the attribute's re match is added
        /***$1 Attribute name $2 operators $3 - $5 are attribute values $3 double quotes $4 Single quotes $5 no quotes ***/
        'ATTR': function(match) {
            / / code
            match[1] = match[1].replace(runescape, funescape);
            // If it's captured from anywhere, put it at $3
            match[3] = (match[3] || match[4] || match[5] | |' ').replace(runescape, funescape);
            // If '~=' is used, Spaces must be left
            if (match[2= = ='~ =') {
                match[3] = "" + match[3] + "";
            }
            return match.slice(0.4);
        },
        / * * * $1 (only | first | last | NTH | NTH - last) $2 child | of -type () $3 the entire contents in parentheses $4 even odd or expression of 2 n + 1 2 n $5 n plus or minus + 2 n - 2 + n - $6 multiples of n 2 $7 operator in 2n + - $8 Last number 1 ***/
        'CHILD': function(match) {
            match[1] = match[1].toLowerCase();
            if (match[1].slice(0.3) = = ='nth') {
                // NTH must have parameters
                if(! match[3]) {
                    Siizle.error(match[0]);
                }
                match[4] = +(match[4]? match[5] + (match[6] | |1) :
                    2 * (match[3= = ='even' || match[3= = ='odd'));
                match[5] = +((match[7] + match[8]) || match[3= = ='odd');
            // No values in parentheses except for NTH
            } else if (match[3]) {
                Sizzle.error(match[0]);
            }
            
            return match;
        },
        /*** $1 Pseudo-class name $2 Everything in brackets $3 Value in quotes $4 Value in single quotes $5 Value in double quotes $6 Value without quotes ***/
        'PESUDO': function(match) {
            var excess,
            // not(:nth-child(2)) // Not (:nth-child(2))unquoted = ! match[6] && match[2];
            if (matchExpr['CHILD'].test(match[0]) {return null;
            }
            // If it is in quotes
            if (match[3]) {
                match[2] = match[4] || match[5] | |' ';
            // If the content in parentheses is still a pseudo-class
            } else if (unquoted && repseudo.test(unquoted) && 
                // Call tokenize recursively
                (excess = tokenize(unquoted, true&&))// Leave the nearest one ()
                // excess is a negative number
                (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) {
                match[0] = match[0].slice(0, excess);
                match[2] = unquoted.slice(0, excess);
            }
            
            return match.slice(0.3); }}// ...
}
Copy the code
  1. TestContext checks the validity of a node as a Sizzle context
function testContext( context ) {
	return context && typeofcontext.getElementsByTagName ! = ="undefined" && context;
}
Copy the code

tokenize

precompiled

The idea of precompiling was introduced in Sizzle after 1.7, and everyone said so, so I said so. The way I understand precompilation is to take what you put in and convert it, by some rule, into another format. There’s another way of saying this, which is called an AST, which I think is a word that’s a little bit more clear to you, and you can read this article if you want.

The precompilation in the Sizzle is the tokenize function, which converts the various selectors into a single object, as a simple example

    var selector = 'a#link > p, .expando';
    // This will be converted to the following
    var tSelector = [
        [
            {
                type: 'TAG'.value: 'a'.match: ['a'] {},type: 'ID'.value: '#link'.match: ['link'] {},type: '>'
                value: '>'
            },
            {
                type: 'TAG'.value: 'p'.match: ['p']}], [{type: 'CLASS'.value: '.expando'.match: ['expando']}]]Copy the code

Function source code

tokenize = Sizzle.tokenize = function(selector, parseOnly) {
    var matched, match, tokens, type,
        soFar, groups, preFilters,
        cached = tokenCache[selector + ' '];
    // There is no cache. If there is cache, return ok
    if (cached) {
        // If parseOnly is used, the cache stores the matched characters.
        // So if there is, there will be no string left so return 0
        // A shallow copy of the cache is returned.
        return parseOnly ? 0 : cached.silce(0)
    }
    soFar = selector;
    groups = [];
    preFilters = Expr.preFilter;
    
    while (soFar) {
        // If it is the first time to enter, or if a comma is matched
        if(! matched || (match = rcomma.exec(soFar))) {// Remove the comma if it is a comma
            if (match) {
                soFar = soFar.slice(match[0].length) || soFar;
            }
            groups.push( (tokens = []) );
        }
        
        matched = false;
        
        // If it is a relational selector
        if ( (match = rcombinators.exec(soFar))) {
            matched = match.shift();
            tokens.push({
                value: matched,
                type: match[0].replace(rtrim, "")}); soFar = soFar.slice(0, matched.length);
        }
        
        // Loop matching
        // TAG CLASS ID ATTR CHILD PESUDO
        for (type in Expr.filter) {
            if(match = matchExpr[type] && (! preFilter[type] || (match = preFilter[type](match)))) { matched = match.shift(); tokens.push({value: matched,
                    type: type,
                    matches: match }); soFar = soFar.slice(matched.length); }}if(! matched) {break; }}ParseOnly returns the length of the remaining string that is not matched
    // If not, see if there are any strings left
    // If yes, the string is invalid
    // If not, save the cache and return a copy
    return paresOnly ?
        soFar.length :
        soFar ? 
            Sizzle.error(selector) : 
            tokenCache(selector, groups).slice(0);
}
Copy the code

If the string is ‘:not(:nth-child(2))’, and the tokenize is entered, and the PESUDO is entered, then preFilter[‘PESUDO’] is used, and the string is matched with $2 and $6 is not. So the tokenize function (paresOnly = true) is repeated, forming the recursion.


The Sizzle function

The Sizzle function is the first function we call the selector. The methods described in the previous two articles were both run when the Sizzle was introduced. This method determines if the string is a simple class tag ID. Such as p, #id,.container. Or whether you can use querySelectorAll directly. If not, go to select.

Function source code

function Sizzle(selector, context, results, seed) {
    var m, i, elem, nid, match, groups, newSelector,
        newContext = context && context.ownerDocument,
        // If you don't pass the context, the default is 9
        nodeType = context ? context.nodeType : 9;
    results = results || [];
    
    // If the selector is empty, or if the selector is not a string, or if the context is not of any of the element Document DocuMetFragment types, return []
    if (typeofselector ! ='string'| |! selector || nodeType ! = =1&& nodeType ! = =9&& noedeType ! = =11) {
            return results;
    }
    if(! seed) {// Set document once, but it usually returns it directly
        setDocument(context);
        context = context || doucment;
        if (documentIsHtml) {
            // If it is simple to select the id tag class case
            if(nodeTpe ! = =11 && (match = rquickExpr.exec(selector))) {
                //ID
                if (m = match[1]) {
                    // is the document node
                    if (nodeType === 9) {
                        if ( (elem = context.getElementById(m)) ) {
                            if (elem.id === m) {
                                results.push(elem);
                                returnresults; }}else {
                            return results;
                        }
                    // If it is an element node
                    // Element nodes do not have a getElememtById method. It can only be selected by document. After selecting the element, it can determine whether the element is included by the current element node
                    } else {
                        if (newContext && (elem = newContext.getElementById(m)) &&
                            contains(context, elem) &&
                            elem.id === m) {
                                results.push(elem);
                                returnresults; }}// Label selection
                } else if (match[2]) {
                    RquickExpr does not match (*), so there is no need to consider compatibility issues, just return all the matched elements
                    push.apply(results, context,getElementByTagName(selector));
                / / class
                } else if ((m = match[3]) && support.getElementsByClassName && 
                context.getElementsByClassName) {
                    push.apply(results, context,getElementsByClassName(m));
                    returnresults; }}// If querySelectorAll is supported, and the string is not a string that has previously appeared that cannot be matched by native methods, and there is no compatibility problem without such a class
            if(support.qsa && ! nonnativeSelevtorCache[selector +' '] && (! rbuggyQSA || ! rbuggyQSA.test(selector)) &&// Exclude object objects(nodeType! = =1|| context.nodeName.toLowerCase() ! = ='object')) {
                newSelector = selector;
                newContext = context;
                
                / / querySelector there is a problem, if not the document. But querySelectorAll element. QuerySelectorAll
                // If the selection string contains relational selectors such as '> ~ +,', the selection results will be different
                // The solution to this problem is to add an ID to the current element and add that ID to the top of the original selection string
                / / by doucment. QuerySelectorAll select elements

                if (nodeType === 1 &&
                    (rdescend.test(selector) || rcombinators.test(selector))) {
                    // Add an ID to its parent set if it is a sibling selector
                    newContext = rsibling.test(selector) && testContext(context.parentNode) ||
                        context;
                    // This is not a very clear judgment. If you know what it means, I hope you can answer it
                    // Here are the English comments
                    // We can use :scope instead of the ID hack if the browser
                    // supports it & if we're not changing the context.
                    if(newContext ! == context || ! support.scope) {if ((nid = context.getAttribute("id"))) {
                            nid = nid.replace(rcssescape, fcssescape);
                        } else {
                            context.setAttribute('id', (nid = expando));
                        }
                    }
                    
                    groups = tokenize(selector);
                    i = groups.length;
                    while(i--) {
                        groups[i] = (nid? The '#' + nid : ':scope') + ' ' + 
                        toSelector(groups[i]);
                    }
                    newSelector.groups.join(', ');
                }
                try {
                    push.apply(results,
                        newContext.querySelectorAll(newSelector)
                    );
                    return results;
                } catch(e) {
                    // Push the string cache that the native method cannot parse
                    nonnatvieSelectorCache(selector, true);
                } finally {
                    if (nid === expando) {
                        context.removeAttribute('id');
                    }
                }
            }
        }
    }
    // Select all items that do not work
    return select(selector.replace(rtrim, '$1'), context, results, seed);
}
Copy the code

conclusion

At this point you should be done with the Sizzle prefixes, and after that you should be able to select the elements. I think we’ll write about select in the next chapter, but that’s it.