preface

In my last article, I wrote about the regular expression for the Sizzle, and I’m going to read the documentation in the order that the Sizzle runs, so I’m going to start with this setDocument function, This function is used to determine the browser’s native selector support, old browser compatibility, querySelectorAll bug detection in each browser, and to initialize the sorting function. This function is executed immediately when the Sizzle is referenced, and is called every time the Sizzle is used. Understanding this function will refer to some of the other variables of the Sizzle, which I’ll add to this article as appropriate for your convenience.

Global variables and methods used by SetDocument

By global, I mean the scope in the IIFE

variable

  1. preferredDoc = window.document
  2. documentHere,documentIt’s a variable, not the document that we know
  3. documentIsHTMLDetermine if the current document is HTML becauseSizzleIs to supportXMLthe
  4. supportThis one is to determine compatibility with an object
  5. ExprWe’ll talk about this in a minute, but this is also an object, and this object is used for searching and filtering elements
  6. hasDuplicateDetermine whether elements are duplicated
  7. sortInputThis is the final set of elements without sorting
  8. rnativeDetermines if a regular pattern is used by a native method
  9. expando = 'sizzle' + 1 * new Date()Used as a unique identifier
  10. runescapeThe previous article had re, determining escape characters

methods

// Check whether it is XML
isXML = function(elem) {
    var namespace = elem.namespaceURI,
        docElem = (elem.ownerDocument || elem).documentElement;
    return! rhtml.test(namespace || docElem && docElem.nodeName ||'HTML');
}
Copy the code
// For validation, this method is used very frequently in the Sizzle, and is used for almost all judgments
function assert(fn) {
    var el = document.createElement("fieldset");
    try {
        return!!!!! fn(el); }catch(e) {
        return false;
    } finally {
        if (el.parentNode) {
            el.parentNode.removeChild(el);
        }
        el = null; }}Copy the code
// For escape
funescape = function(escape, nonHex) {
    var high = '0x' + escape.slice( 1 ) - 0x10000;
    return noHex ? 
        noHex:
        high < 0 ? 
            String.fromCharCode(high + 0x10000) :
            String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00)}Copy the code
function siblingCheck(a, b) {
    var cur = b && a,
        diff = cur && a.nodeType === 1 && b.nodeType == 1 &&
            a.sourceIndex - b.sourceIndex;
    if (diff) {
        return diff
    }
    if (cur) {
        while((cur = cur.nextSibling)) {
            if (cur === b) {
                return - 1}}}return a ? 1 : - 1;
}
Copy the code

SetDocument method

The setDocument method is executed once in the IIFE, and has no arguments, for initialization. The method is divided into several parts. 2. Set Expr according to compatibility, 3. QuerySelectorAll compatibility problem, 4. Set sort.

Parameter and iframe compatibility and performance optimization

/ * @ param Element {| Object} an Element or document Element is used to set the document variable * /
setDocument = Sizzle.setDocument = function(node) {
    var hasCompare, subWindow,
        doc = node ? node.ownerDocument || node : preferredDoc;
    
    // If the element is not a document element, or there is no HTML, or the element is under the same document as the last call
    // Let's go straight back and optimize the performance
    if (doc == document|| doc.nodeType ! = =9| |! doc.documentElement) {return document;
    }
    
    // Update the external global variable
    document = doc;
    docElem = document.documentElement; documentIsHTML = ! isXML(document);
    
    // This is iframe compatible
    if( perferredDoc ! =document && 
        (subWindow = document.defaultView) && subWindow.top ! == subWindow ) {if ( subWindow.addEventListener ) {
                subWindow.addEventListener('unload', unloadHandler, false);
            } else if ( subWindow.attachEvent ) {
                subWindow.attachEvent('onunload', unloadHandler); }}}Copy the code

This part is about updating global variables, performance optimization, and iframe compatibility.

Support Compatibility with native apis of the browser

// IE/Edge and older browsers do not support: Scope pseudo-class
// Check whether the selected element is present (length should be 0 because there is no pseudo-class :scope added)
// If length is not equal to 0, then not supported; If it's 0, it's support
support.scope = assert( function( el ) {
    docElem.appendChild( el ).appendChild( document.createElement('div'));return typeofel.querySelectorAll ! = ='undefined' &&
        !el.querySelectorAll(':scope fieldset div').length
} );

// Check whether attributes and attributes overlap
// After assigning a value to an element's attribute, IE can get the value by getting the same attribute name
support.attributes = assert( function( el ) {
    el.className = 'i';
    return! el.getAttribute('className'); });// Check whether getElementsByTagName returns only element nodes
support.getElementsByTagName = assert( function( el ) {
    el.appendChild(document.createComment(' '));
    return! el.getElementsByTagName(The '*').length
} );

// Determine whether getElementsByClassName is a native method for browsing
support.getElementsByClassName = rnative.test(document.getElementsByClassName);

// Check if getElementById returns the element by name
// However, older versions of the getElementById method cannot get the id set programmatically
// So use getElementsByName to see if you can get the ID through this method
support.getById = assert( function( el ) {
    docElem.appendChild(el).id = expando;
    return !document.getElementsByName || !document.getElementsByName(expando).length; })Copy the code

Set the Expr

Here is to set Expr according to the support situation just now. About Expr, I think I will write a separate article about it. Although I haven’t read it, I feel that it should be very important, so I don’t need to write it slowly.

//ID
if (support.getById) {
    Expr.filter["ID"] = function(id) {
        / / code
        var attrId = id.replace(runescape, funescape);
        return function(elem) {
            return elem.getAttribute('id') == attrId; }}; Expr.find["ID"] = function(id, context) {
        if (typeofcontext.getElementById ! = ='undefined' && documentIsHTML) {
            var elem = context.getElementById(id);
            return elem ? [elem] : []
        }
    }
} else {
    Expr.filter["ID"] = function(id) {
        / / code
        var attrId = id.repalce(runescape, funescape);
        return function(elem) {
            var node = typeofelem.getAttributeNode ! = ='undefined' &&
                elem.getAttributeNode('id');
            returnnode && node.value === attrId; }};// In IE 6-7, getElementById is not a reliable lookup method
    Expr.find["ID"] = function(id, context) {
        if (typeofcontext.getElemntById ! = ='undefined' && documentIsHTML) {
            var node, i, elems,
                elem = context.getElementById(id);
            if (elem) {
                node = elem.getAttributeNode('id');
                if (node && node.value === id) {
                    return [elem];
                }
                
                elems = context.getElementsByName(id);
                i = 0;
                while(elem = elems[i++]) {
                    node = elem.getAttributeNode('id');
                    if (node && node.value === id) {
                        return [elem]
                    }
                }
            }
            return[]; }}}Copy the code

GetElementById in older versions of IE, namely IE6 and 7, returns the first element whose name or ID matches the value. Therefore, after obtaining the element, you can correctly determine the element by obtaining the id attribute of the element and judging whether it is equal to the input value. But there is also the old IE getAttribute problem, which can be seen in this blog post by Stu.


//Tag
Expr.find['TAG'] = support.getElementsByTagName ? 
    function(tag, context) {
        if (typeofcontext.getElementsByTagName ! = ='undefined') {
            return context.getElementsByTagName(tag);
        // The DocumentFragment node has no getElementsByTagName method
        } else if (support.qsa) {
            returncontext.querySelectorAll(tag); }} :function(tag, context) {
        var elem,
            tmp = [],
            i = 0,
            results = context.getElementsByTagName(tag);
        // Filter out nodes that are not elements
        if (tag === The '*') {
            while ( ( elem = results[i++] ) ) {
                if (elem.nodeType === 1) { tmp.push(elem); }}return tmp;
        }
        return results;
    }
Copy the code

GetElementsByTag is an easy one. If you select all, you filter out the non-elements.


//Class
Expr.find['CLASS'] = support.getElementsByClassName && function(className, context) {
    if (typeofcontext.getElementsByClassName ! = ='undefined' && documentIsHTML) {
        returncontext.getElementsByClassName(className); }}Copy the code

GetElementsByClassName is something you use, something you don’t.


QuerySelectorAll compatibility problem

We first determine whether the browser supports the querySelectorAll and matches methods. If so, then determine compatibility issues. This part is difficult to read, because many of the compatibilities are not encountered, almost according to the comments, these compatibilities can not be too careful, the key is to understand the idea, such as intentionally looking for an error to catch the external function instead of executing the following code. I thought it was really cool when I first saw it.

rbuggyMatches = [];
rbuggyQSA = [];

if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) {
    assert( function (el) {
        var input;
        docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
        "<select id='" + expando + "-\r\\' msallowcapture=''>" + 
        "<option selected=''></option></select>";
        
        //^= $= *=
        if (el.querySelectorAll("[msallowcapture^='']").length) {
            rbuggyQSA.push("[* ^ $] =" + whitespace + "* (? : '| \ "\")");
        }
        
        //IE8 does not handle the Boolean and 'value' attributes properly
        if(! el.querySelectorAll("[selected]").length) {
            rbuggyQSA.push("\ \ [" + whitespace + "* (? :value|" + booleans + ")");
        }
        
        //\r is a space so it should match
        if(! el.querySelectorAll("[id~=" + expando + "-]").length) {
            rbuggyQSA.push('~ =');
        }
        
        input = document.createElement("input");
        input.setAttribute("name"."");
        el.appendChild(input);
        
        // In some cases, IE11 and Edge cannot find the element [name=""]
        // But the interesting thing is that the old IE has no problem
        if(! el.querySelectorAll("[name='']").length) {
            rbuggQSA.push("\ \ [" + whitespace + "*name" + whitespace + "* =" + whitespace + "* (? : '| \ "\")");
        }
        
        //IE8 will report an error here, and the following code will not be executed by the assert method catch
        // Select the selected option
        if(! el.querySelectorAll(":checked").length) {
            rbuggyQSA.push(":checked");
        }
        
        //Safari 8+, IOS 8+
        //ID + sibling selector is invalid
        if(! el.querySelectorAll("a#" + expando + "+ *").length) {
            rbuggyQSA.push(". #. + [+ ~]. "");
        }
        
        // Only old Firefox does not report errors when using incorrect escape characters
        // If there is an error, it will go straight to the catch of assert, and it will not run the last push.
        el.querySelectorAll('\\\f');
        rbuggyQSA.push("[\\r\\n\\f]"); }); assert(function (el) {
        el.innerHTML = "<a href='' disabled='disabled'></a>" + 
            "<select disabled='disabled'><option/></select>";
        
        var input = document.createElement("input");
        input.setAttribute("type"."hidden");
        el.appendChild( input ).setAttribute("name"."D");
        
        // The name attribute is case sensitive
        if (el.querySelectorAll("[name=d]").length) {
            rbuggyQSA.push("name" + whitespace + "* / * ^ $|! ~? =");
        }
        
        // Option and input should be selected properly
        // IE8 will not execute the test below
        if (el.querySelectorALL(':enabled').length ! = =2) {
            rbuggyQSA.push(":enabled".":disabled");
        }
        
        // IE9-11 :disabled Does not select a subset of disabled fieldSet elements
        docElem.appendChild(el).disabled = true;
        if (el.querySelectorAll(':disabled').length ! = =2) {
            rbuggyQSA.push(':enabled'.':disabled');
        }
        
        // Opera 10-11 does not report an invalid pseudo-class;
        // This rule is not added by catch if an error is reported
        // If there is no error, add it
        el.querSelectorAll('*,:x');
        rbuggyQSA.push('. * :); }); }if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || 
    docElem.webkitMatchesSelector ||
    docElem.mozMatchesSelector ||
    docElem.oMatchesSelector ||
    docElem.msMactesSelector) ) ) ) {
    
    assert(function(el) {
        // Matches can be used to detect elements that are not in the DOM tree
        support.disconnectedMatch = matches.call(el, "*");
        
        // Gecko only returns false when it should
        // Catch the error
        matches.call(el, '[s!= ""]:x');
        rbuggyMatches.push('! = ', pseudos ); }); }// Convert to regular expressions or relationships
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|")); rbuggyMatches = rbuggyMatches.length &&new RegExp( rbuggyMatches.join( "|"));Copy the code

The sorting

The idea behind the Sizzle sorting is to use the native array.prototype. sort method, so sorting is basically a callback function to sort. We set up the rules and let the browser sort for us. The main thing that’s used for sorting is the compareDocumentPosition method, and the Sizzle is going to tell if there’s a method, and if there isn’t, then it’s going to iterate.

hasCompare = rnative.test(docElem.compareDocumentPosition);

contains = hasCompare || rnative.test(docELem.contains) ? 
    function (a, b) {
        var adown = a.nodeType === 9 ? a.documentElement : a,
            bup = b && b.parentNode;
        returna === bup || !! (bup && bup.nodeType ===1 && (
            adown.contains ?
            adown.contains( bup ) :
            a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16))} :function (a, b) {
        if (b) {
            while ((b = b.parentNode)) {
                if (a === b) {
                    return true; }}}return false;
    }
    
    sortOrder = hasCompare ? 
    function(a, b) {
        if (a === b) {
            hasDuplicate = true;
            return 0;
        }
        // If only one has compareDocumentPosition
        // Who has who in front
        varcompare = ! a.compareDocumentPosition - ! b.compareDocumentPostion;if (compare) {
            return compare;
        }
        
        // Check to see if one is under one document
        // If not, there is no relationship between the two nodes
        compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
        a.compareDocumentPosition(b) :
        1;
        
        Detached /** suport.sortdetached = assert(function(el) {this should return 1 but may return 4 el.compareDocumentPostion(document.createElement('fieldset')) & 1; }) * * /
        
        // If there are elements that are not in the DOM tree
        // Who is in front of the DOM
        // If both of them are not in place, arrange them in their original position
        if (compare & 1| | (! suport.sortDetached && compare === b.compareDocumentPosition(a)) ) {if (a == document || a.ownerDocument == preferredDoc && contains(preferredDoc, a)) {
                return - 1;
            }
            
            if (b == document || b.ownerDocument == preferredDoc &&
            contains(preferredDoc, b)) {
                return 1;
            }
            
            return sortInput ? 
                (indexOf(sortInput, a) - indexOf(sortInput, b)) : 
                0;
        }
        return compare & 4 ? - 1 : 1;
    } :
    function (a, b) {
        if (a === b) {
            hasDuplicate = true;
            return 0;
        } 
        var cur,
            i = 0,
            aup = a.parentNode,
            bup = b.parentNode,
            ap = [a],
            bp = [b];
        // If there is no parent, then either document or not in the DOM tree
        if(! aup || ! bup) {return a == document ? - 1 : // If a is a document, a comes first
                b == document ? 1 :     // If b is a document, b comes first
                aup ? - 1 :              // If a is in the DOM tree, a is in front
                bup ? 1 :               // If b is in the DOM tree, b is in front
                sortInput ?             // If both of them are not in the same order
                (indexOf(sortInput, a) - indexOf(sortInput, b)) :
                0;
        } else if (aup === bup) {
            // If the parent is the same, then nextSibling can be directly determined
            return siblingCheck(a, b);
        } 
        // If not, start from scratch
        cur = a;
        while( (cur = cur.parentNode) ) {
            ap.unshift(cur);
        }
        
        cur = b;
        while( (cur = cur.parentNode) ) {
            bp.unshift(cur);
        }
        
        while(ap[i]  === bp[i]) {
            i++;
        };
        
        return i ? 
            NextSibling = nextSibling = nextSibling = nextSibling = nextSibling = nextSibling
            siblingCheck(ap[i], bp[i]) :
            ap[i] === perferredDoc ? - 1 :
            bp[i] === preferredDoc ?  1 :
            0
    }
Copy the code

SetDocument full version

setDocument = Sizzle.setDocument = function(node) {
    var hasCompare, subWindow,
        doc = node ? node.ownerDocument || node : preferredDoc;
    
    if (doc == document|| doc.nodeType ! = =9| |! doc.documentElement) {return document;
    }
    
    document = doc;
    docElem = document.documentElement; documentIsHTML = ! isXML(document);
    
    if(perferredDoc ! =document &&
        (subWIndow = document.defaultView) && subWindow.top ! == subWindow) {if (subWIndow.addEventListener) {
                subWindow.addEventListener('unload', unloadnHandler, false);
            } else if (subWindow.attachEvent) {
                subWindow.attachEvent('onunload', unloadHandler);
            }
        }
    
    support.scope = assert( function(el) {
        docElme.appednChild(el).appendChild(document.createElement('div'));
        return typeofel.querySelectorAll ! = ='undefined' &&
            !el.querySelector(':scope fieldset div').length; }); support.attributes = assert(function(el) {
        el.className = 'i';
        return! el.getAttribute('className');
    });
    
    support.getElementsByTagName = assert( function(el) {
        el.appendChild(document.createComment"");
        return! el.getElementsByTagName(The '*').length; }); support.getElementsByClassName = rnative.test(document.getElementsByClassName);
    
    support.getById = assert( function(el) {
        docElem.appendChild(el).id = expando;
        return !document.getElementsByName || !document.getElementsByName(expando).length; });//ID
    if (support.getById) {
        Expr.filter["ID"] = function(id) {
            var attrId = id.replace(runescape, funescape);
            return function(elem) {
                return elem.getAttribute('id') === attrId;
            }
        }
        Expr.find["ID"] = function(id, context) {
            if (typeofcontext.getElementById ! = ='undeifined' && documentIsHTML) {
                var elem = context.getElementById( id );
                returnelem ? [elem] : []; }}}else {
        Expr.filter["ID"] = function(id) {
            var attrId = id.replace(runescape, funescape);
            return function(elem) {
                var node = typeofelem.getAttributeNode ! = ='undefined' && 
                    elem.getAttributrNode('id');
                returnnode && node.value === attrId; }}; Expr.find["ID"] = function(id, context) {
            if (typeofcontext.getElementById ! = ='undefined' && documentIsHTML) {
                var node, i, elems,
                    elem = context.getElemetById(id);
                if (elem) {
                    node = elem.getAttributeNode("id");
                    if (node && node.value === attrId) {
                        return [elem];
                    }
                    
                    elems = document.getElementsByName(id);
                    i = 0;
                    while( (elem = elems[i++]) ) {
                        node = elem.getAttributeNode("id");
                        if (node && node.value === attrId) {
                            returnelem; }}}return[]; }}}//Tag
    Expr.find["TAG"] = support.getElementsByTagName ?
        function(tag, context) {
            if (typeofcontext.getElementsByTagName ! = ='undefined') {
                return context.getElementsByTagName(tag);
            } else if (support.qsa) {
                returncontext.querySelectorAll(tag); }} :function(tag, context) {
            var elem,
                tmp = []
                i = 0,
                results = context.getElementsByTagName(tag);
            if (tag === "*") {
                while ( (elem = results[i++]) ) {
                    if (elem.nodeType === 1) { tmp.push(elem); }}return tmp;
            }
            return results;   
        }
    
    //Class
    Expr.find["CLASS"] = support.getElementsByClassName && function(className, context) {
        if (typeofcontext.getElementsByClassName ! = ='undefined' && documentIsHTML) {
            return context.getElementsByClassName(className);
        }
    }
    rbuggyMatches = [];
    rbuggyQSA = [];
    
    if ((suuport.qsa = rnatvie.test(document.querySelelctorAll))) {
        assert( function (el) {
            var input;
            docElem.appendChild(el).innerHTML = "<a id='" + expando + "'></a>" +
            "<select id='" + expando + "-\r\\' msallowcapture=''>" + 
            "<option selected=''></option></select>";
            
            if (el.querySelectorAll("[msallowcapture^='']").length) {
                rbuggyQSA.push("[* ^ $] =" + whitespace + "* (? : '| \ "\")");
            }

            if(! el.querySelectorAll("[selected]").length) {
                rbuggyQSA.push("\ \ [" + whitespace + "* (? :value|" + booleans + ")");
            }
            
            if(! el.querySelectorAll("[id~=" + expando + "-]").length) {
                rbuggyQSA.push('~ =');
            }
            
            input = document.createElement("input");
            input.setAttribute("name"."");
            el.appendChild(input);
            
            if(! el.querySelectorAll("[name='']").length) {
                rbuggQSA.push("\ \ [" + whitespace + "*name" + whitespace + "* =" + whitespace + "* (? : '| \ "\")");
            }
            
            if(! el.querySelectorAll(":checked").length) {
                rbuggyQSA.push(":checked");
            }
            
            if(! el.querySelectorAll("a#" + expando + "+ *").length) {
                rbuggyQSA.push(". #. + [+ ~]. "");
            }
            
            el.querySelectorAll('\\\f');
            rbuggyQSA.push("[\\r\\n\\f]"); }); assert(function (el) {
            el.innerHTML = "<a href='' disabled='disabled'></a>" + 
                "<select disabled='disabled'><option/></select>";
            
            var input = document.createElement("input");
            input.setAttribute("type"."hidden");
            el.appendChild( input ).setAttribute("name"."D");
            
            if (el.querySelectorAll("[name=d]").length) {
                rbuggyQSA.push("name" + whitespace + "* / * ^ $|! ~? =");
            }

            if (el.querySelectorALL(':enabled').length ! = =2) {
                rbuggyQSA.push(":enabled".":disabled");
            }
            
            docElem.appendChild(el).disabled = true;
            if (el.querySelectorAll(':disabled').length ! = =2) {
                rbuggyQSA.push(':enabled'.':disabled');
            }
            
            el.querSelectorAll('*,:x');
            rbuggyQSA.push('. * :); }); }if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || 
        docElem.webkitMatchesSelector ||
        docElem.mozMatchesSelector ||
        docElem.oMatchesSelector ||
        docElem.msMactesSelector) ) ) ) {
        
        assert(function(el) {
            support.disconnectedMatch = matches.call(el, "*");

            matches.call(el, '[s!= ""]:x');
            rbuggyMatches.push('! = ', pseudos ); }); } rbuggyQSA = rbuggyQSA.length &&new RegExp( rbuggyQSA.join( "|")); rbuggyMatches = rbuggyMatches.length &&new RegExp( rbuggyMatches.join( "|"));//Contains
    hasCompare = rnative.test(docElem.compareDocumentPostion);
    
    contains = hasCompare || rnative.test(docElem.contains) ?
        function(a, b) {
            var adown = a.nodeType === 9 ? a.documentElement : a,
                bup = b && b.parentNode;
            returna === bup || !! ( bup && bup.nodeType ===1 && (     adown.contains ? 
                    adown.contains(bup) :
                    a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16;) ); } :function (a, b) {
            if (b) {
                while ( (b = b.parentNode) ) {
                    if (a === b) {
                        return true; }}}return false;
        }
        
    //Sorting
    sortOrder = hasCompare ?
    function(a, b) {
        if (a === b) {
            hasDuplicate = true;
            return 0;
        }
        
        varcompare = ! a.compareDocumentPosition - ! b.compareDocumentPosition;if (compare) {
            return compare;
        }
        
        compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
            a.compareDocumentPosition(b) : 
            1
        if (compare & 1| | (! support.sortDetached && b.compareDocumentPosition(a) === compare )) {if (a == document || a.ownerDocument == preferredDoc &&
                contains(preferredDoc, a)) {
                return - 1;
            }
            
            if (b == document || b.ownerDocument == preferredDoc &&
                contains(preferredDoc, b)) {
                return 1;
            }
            
            return sortInput ? 
                (indexOf(sortInput, a) - indexOf(sortInput,b)) :
                0;
        }
        return compare & 4 ? - 1 : 1;
    } :
    function(a, b) {
        if (a === b) {
            hasDuplicate = true;
            return  0;
        }
        
        var cur,
            i = 0,
            aup = b.parentNode,
            bup = b.parentNode,
            ap = [ a ],
            bp = [ b ];
        
        if(! aup || ! bup) {return a == document ? - 1 :
                b == document ? 1 :
                aup ? - 1 :
                bup ? 1 :
                sortInput ? 
                (indexOf(sortInput, a) - indexOf(sortInput, b)) :
                0;
        } else if (aup === bup) {
            return siblingCheck(a, b);
        }
        cur = a;
        while( (cur = cur.parentNode) ) {
            ap.unshift(cur);
        }
        cur = b;
        while( (cur = cur.parentNode) ) {
            bp.unshif(cur);
        }
        while (ap[i] === bp[i]) {
            i++
        }
        return i ?
            siblingCheck(ap[i] : bp[i]) :
            ap[i] = preferredDoc ? - 1 :
            bp[i] = preferredDoc ? 1 :
            0;
    }
    return document;
}
Copy the code