Sizzle 源码分析(二):SetDocument

612 阅读7分钟

前言

上一篇文章写的关于Sizzle正则表达式,我打算就按照Sizzle的运行顺序进行阅读文档,所以一马当先就是这个setDocument函数了,这个函数主要做了浏览器的原生选择器支持情况判断、老浏览器的兼容、querySelectorAll在个浏览器的bug排查和排序函数的初始化。函数会在Sizzle被引用的时候立即执行一次,并且在每次使用Sizzle的时候都会调用,读懂这个函数会涉及Sizzle一些其他的变量,这里我会酌情加到这篇文章里面,方便大家阅读。

SetDocument用到的全局变量与方法

这里的全局指的是IIFE中的作用域

变量

  1. preferredDoc = window.document
  2. document 这里的document是个变量不是我们所熟知的那个document
  3. documentIsHTML 判断当前文档是不是HTML 因为Sizzle是支持XML
  4. support 这个是判断兼容用的是一个对象
  5. Expr这个以后会单说,这个也是一个对象,这个对象是做搜索以及过滤元素用的
  6. hasDuplicate 判断元素是否有重复
  7. sortInput 这个就是没有进行排序的最终元素集
  8. rnative 判断是不是原生方法用的正则
  9. expando = 'sizzle' + 1 * new Date() 作为唯一标识使用
  10. runescape 上一篇文章有, 正则, 判断转义字符

方法

//判断是否为XML
isXML = function(elem) {
    var namespace = elem.namespaceURI,
        docElem = (elem.ownerDocument || elem).documentElement;
    return !rhtml.test(namespace || docElem && docElem.nodeName || 'HTML');
}
//用于验证,这个方法在Sizzle中使用非常频繁, 几乎所有的判断兼用都用了这个方法
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;
    }
}
//转义用的
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)
}
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;
}

SetDocument方法

setDocument方法会在IIFE中执行一次,并且没有参数,用来初始化。
方法分为几个部分。1.原生搜索方法兼容,2.根据兼容设置Expr,3.querySelectorAll兼容问题,4.设置排序。

参数以及iframe兼容和性能优化

/*
@param {Element|Object} 一个元素或者文档元素用来设置document变量
*/
setDocument = Sizzle.setDocument = function(node) {
    var hasCompare, subWindow,
        doc = node ? node.ownerDocument || node : preferredDoc;
    
    // 这里如果不是document元素或者没有html,再或者和上一次调用的时候元素处在同一个document下面
    //那就直接返回, 性能优化
    if (doc == document || doc.nodeType !== 9 || !doc.documentElement) {
        return document;
    }
    
    //更新外部的全局变量
    document = doc;
    docElem = document.documentElement;
    documentIsHTML = !isXML(document);
    
    //这块就是兼容iframe的
    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);
            }
        }
}

这部分就是做了更新全局变量、性能优化、以及iframe的兼容。

support浏览器原生API兼容情况

// IE/Edge和老浏览器不支持:scope这个伪类
// 判断会不会有选中的元素(因为没有添加:scope这个伪类, 所以length应该为0)
// 如果length不等于0,那么不支持; 如果为0,就是支持
support.scope = assert( function( el ) {
    docElem.appendChild( el ).appendChild( document.createElement('div') );
    return typeof el.querySelectorAll !== 'undefined' &&
        !el.querySelectorAll(':scope fieldset div').length
} );

//判断属性与特性是否重叠
//老IE在给元素的特性赋值后, 可以通过获取相同的属性名获取到该值
support.attributes = assert( function( el ) {
    el.className = 'i';
    return !el.getAttribute('className');
} );

//检查getElementsByTagName是否只返回元素节点
support.getElementsByTagName = assert( function( el ) {
    el.appendChild(document.createComment(''));
    return !el.getElementsByTagName('*').length
} );

//判断getElementsByClassName是否为浏览其原生方法
support.getElementsByClassName = rnative.test(document.getElementsByClassName);

// 检查如果getElementById是否是通过name返回元素的
// 但是老版本的getElementById方法不能拿到编程方式设置的id
// 所以使用getElementsByName看看通过这个方法时候能获取id
support.getById = assert( function( el ) {
    docElem.appendChild(el).id = expando;
    return !document.getElementsByName || !document.getElementsByName(expando).length;
} )

设置Expr

这里就是根据刚刚的support情况去设置Expr。关于Expr我估计我会单独用一篇文章去写它,虽然没看过它,但是感觉这兄弟应该很重要,不急慢慢写。

//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 (typeof context.getElementById !== 'undefined' && documentIsHTML) {
            var elem = context.getElementById(id);
            return elem ? [elem] : []
        }
    }
} else {
    Expr.filter["ID"] = function(id) {
        //转码
        var attrId = id.repalce(runescape, funescape);
        return function(elem) {
            var node = typeof elem.getAttributeNode !== 'undefined' &&
                elem.getAttributeNode('id');
            return node && node.value === attrId;
        }
    };
    //IE 6 -7 的时候getElementById不是一个可靠的查找方法
    Expr.find["ID"] = function(id, context) {
        if (typeof context.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 [];
        }
    }
}

getElementById在老IE版本中也就是IE6、7中,会返回第一个name或者id为匹配值的元素,所以在获取到元素后,通过获取元素的id属性并判断是否等于输入的值,才能正确判断元素。但是还有一个问题是就是老的IE的getAttribute的问题,具体可以看司徒大大写的这篇博客


//Tag
Expr.find['TAG'] = support.getElementsByTagName ? 
    function(tag, context) {
        if (typeof context.getElementsByTagName !== 'undefined') {
            return context.getElementsByTagName(tag);
        //DocumentFragment节点是没有getElementsByTagName方法的
        } else if (support.qsa) {
            return context.querySelectorAll(tag);
        }
    } : 
    function(tag, context) {
        var elem,
            tmp = [],
            i = 0,
            results = context.getElementsByTagName(tag);
        //把不是元素的Node节点过滤掉
        if (tag === '*') {
            while ( ( elem = results[i++] ) ) {
                if (elem.nodeType === 1) {
                    tmp.push(elem);
                }
            }
            return tmp;
        }
        return results;
    }

getElementsByTag这个就比较简单了,如果是全选话,把不是元素的节点过滤掉就好了。


//Class
Expr.find['CLASS'] = support.getElementsByClassName && function(className, context) {
    if (typeof context.getElementsByClassName !== 'undefined' && documentIsHTML) {
        return context.getElementsByClassName(className);
    }
}

getElementsByClassName就是有就用,没有就不用了。


querySelectorAll兼容问题

这里先判断了浏览器支不支持querySelectorAllmatches这两个方法。如果支持的话,再去判断兼容问题。这部分看的时候比较难懂,因为很多兼容都没有碰到过,几乎都是按照注释来看的,这些兼容可以不用太留心,关键是理解一下思路,比如故意找报错让外部的函数catch而不再执行下面的代码。我第一次看的时候觉得非常的酷。

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 还是布尔属性和'value'属性不能正常处理
        if (!el.querySelectorAll("[selected]").length) {
            rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")");
        }
        
        //\r是空格所以应该可以匹配的到
        if (!el.querySelectorAll("[id~=" + expando + "-]").length) {
            rbuggyQSA.push('~=');
        }
        
        input = document.createElement("input");
        input.setAttribute("name", "");
        el.appendChild(input);
        
        //IE11和Edge在某些情况下找不到[name=""]的元素
        //但是有意思的是 老的IE却没有问题
        if (!el.querySelectorAll("[name='']").length) {
            rbuggQSA.push("\\[" + whitespace + "*name" + whitespace + "*=" + whitespace + "*(?:''|\"\")");
        }
        
        //IE8在这里会报错, 然后被assert方法catch下面的代码不会再执行
        //这里是可以选择select中被selected的option的
        if (!el.querySelectorAll(":checked").length) {
            rbuggyQSA.push(":checked");
        }
        
        //Safari 8+, IOS 8+
        //ID + 兄弟选择器会失效
        if (!el.querySelectorAll("a#" + expando + "+*").length) {
            rbuggyQSA.push(".#.+[+~]");
        }
        
        //当用的是错误的转义字符的话, 只有老火狐不会报错
        //如果报错, 还是会直接走assert的catch了, 就不会运行最后这个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");
        
        // name属性区分大小写的问题
        if (el.querySelectorAll("[name=d]").length) {
            rbuggyQSA.push("name" + whitespace + "*[*^$|!~]?=");
        }
        
        // option 和 input 应该被正常选中
        // IE8在这里会报错 下面的test都不再执行
        if (el.querySelectorALL(':enabled').length !== 2) {
            rbuggyQSA.push(":enabled", ":disabled");
        }
        
        // IE9-11 :disabled不选择disabled的fieldset元素的子集
        docElem.appendChild(el).disabled = true;
        if (el.querySelectorAll(':disabled').length !== 2) {
            rbuggyQSA.push(':enabled', ':disabled');
        }
        
        // Opera 10-11 逗号后伪类无效是不会报错的;
        // 这里还是 如果报错了 就直接被catch不添加这个规则
        // 如果没报错就加进去
        el.querSelectorAll('*,:x');
        rbuggyQSA.push(',.*:');
    } );
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || 
    docElem.webkitMatchesSelector ||
    docElem.mozMatchesSelector ||
    docElem.oMatchesSelector ||
    docElem.msMactesSelector) ) ) ) {
    
    assert(function(el) {
        // 不在DOM树上的元素, 是否可以用matches检测
        support.disconnectedMatch = matches.call(el, "*");
        
        // 这里本应该报错的, 但是Gecko只返回false
        // 这里也是,如果报错直接catch
        matches.call(el, '[s!= ""]:x');
        rbuggyMatches.push('!=', pseudos );    
    } );
}

// 转成正则表达式 或关系
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) );

排序

Sizzle的排序思路是使用了原生的Array.prototype.sort方法,所以所谓排序其实就是配置sort的回调函数,我们设置好规则,让浏览器来帮我们进行排序。主要排序用的就是compareDocumentPosition这个方法,Sizzle会先判断有没有这个方法, 如果没有那就只能遍历了。

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;
        return a === 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;
        }
        //如果只有一个有compareDocumentPosition
        //谁有谁排前面
        var compare = !a.compareDocumentPosition - !b.compareDocumentPostion;
        if (compare) {
            return compare;
        }
        
        //查看一下是不是一个在一个document下
        //如果不是的话 那么两个节点之间不存在关系
        compare = (a.ownerDocument || a) == (b.ownerDocument || b) ?
        a.compareDocumentPosition(b) :
        1;
        
        /**
        suport.sortDetached = assert(function( el ) {
            这里应该返回1 但是有可能返回4
            return el.compareDocumentPostion(document.createElement('fieldset')) & 1;
        }) 
        **/
        
        // 如果这里是有不在DOM树中的元素
        // 谁在DOM中谁在前面
        // 如果两个都不在 那就按原本的位置排列
        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];
        // 如果没有父节点 那么不是document就是不在DOM树上
        if (!aup || !bup) {
            return a == document ? -1 : //如果a是document, a在前面
                b == document ? 1 :     //如果b是document, b在前面
                aup ? -1 :              //如果a在DOM树上, a在前面
                bup ? 1 :               //如果b在DOM树上, b在前面
                sortInput ?             //如果都不在 那就正常排序把
                (indexOf(sortInput, a) - indexOf(sortInput, b)) :
                0;
        } else if (aup === bup) {
            // 如果父级相同 那么可以通过nextSibling直接判断
            return siblingCheck(a, b);
        } 
        // 如果都不是的话 那就从头开始比
        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判断谁在前面
            siblingCheck(ap[i], bp[i]) :
            ap[i] === perferredDoc ? -1 :
            bp[i] === preferredDoc ?  1 :
            0
    }

setDocument完整版

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 typeof el.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('*').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 (typeof context.getElementById !== 'undeifined' && documentIsHTML) {
                var elem = context.getElementById( id );
                return elem ? [elem] : [];
            }
        }
    } else {
        Expr.filter["ID"] = function(id) {
            var attrId = id.replace(runescape, funescape);
            return function(elem) {
                var node = typeof elem.getAttributeNode !== 'undefined' && 
                    elem.getAttributrNode('id');
                return node && node.value === attrId;
            }
        };
        Expr.find["ID"] = function(id, context) {
            if (typeof context.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) {
                            return elem;
                        }
                    }
                }
                return [];
            }
        }
    }
    
    //Tag
    Expr.find["TAG"] = support.getElementsByTagName ?
        function(tag, context) {
            if (typeof context.getElementsByTagName !== 'undefined') {
                return context.getElementsByTagName(tag);
            } else if (support.qsa) {
                return context.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 (typeof context.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;
            return a === 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;
        }
        
        var compare = !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;
}