【问题标题】:cross browser compare document position跨浏览器比较文档位置
【发布时间】:2026-01-16 10:00:02
【问题描述】:

DOM4 compareDocumentPosition

我想实现 compareDocumentPosition。 Resig 发了一个great start at doing just this。我已经把他的代码整理好了

function compareDocumentPosition(other) {
    var ret = 0;
    if (this.contains) {
        if (this !== other && this.contains(other)) {
            ret += 16;
        }
        if (this !== other && other.contains(this)) {
            ret += 8;
        }
        if (this.sourceIndex >= 0 && other.sourceIndex >= 0) {
            if (this.sourceIndex < other.sourceIndex) {
                ret += 4;
            }
            if (this.sourceIndex > other.sourceIndex) {
                ret += 2;
            }
        } else {
            ret += 1;
        }
    } 
    return ret;
}

这适用于Element,但不适用于TextDocumentFragment。这是因为 IE8 没有在这些节点上提供.sourceIndex。 (它也不给.contains,但我已经解决了这个问题)

如何有效地编写对应于DOCUMENT_POSITION_FOLLOWINGDOCUMENT_POSITION_PRECEDING+=4+=2 位。

作为额外参考,这两个由 DOM4 定义的树顺序定义

如果 A 和 B 在同一棵树中并且 A 在树顺序中位于 B 之前,则对象 A 在对象 B 之前。

如果 A 和 B 在同一棵树中,并且 A 按树顺序在 B 之后,则对象 A 跟随对象 B。

树的顺序是前序,深度优先遍历。

大多数现代浏览器都实现了这一点(包括 IE9)。所以你只需要在 IE8 中工作的东西(我不关心 IE6/7,但如果它工作得很好!)

【问题讨论】:

    标签: javascript internet-explorer-8 shim dom4


    【解决方案1】:
    function recursivelyWalk(nodes, cb) {
        for (var i = 0, len = nodes.length; i < len; i++) {
            var node = nodes[i];
            var ret = cb(node);
            if (ret) {
                return ret;
            }
            if (node.childNodes && node.childNodes.length) {
                var ret = recursivelyWalk(node.childNodes, cb);
                if (ret) {
                    return ret;
                }
            }
        }
    }
    
    function testNodeForComparePosition(node, other) {
        if (node === other) {
            return true;
        }
    }
    
    function compareDocumentPosition(other) {
        function identifyWhichIsFirst(node) {
            if (node === other) {
                return "other";
            } else if (node === reference) {
                return "reference";
            }
        }
    
        var reference = this,
            referenceTop = this,
            otherTop = other;
    
        if (this === other) {
            return 0;
        }
        while (referenceTop.parentNode) {
            referenceTop = referenceTop.parentNode;
        }
        while (otherTop.parentNode) {
            otherTop = otherTop.parentNode;
        }
    
        if (referenceTop !== otherTop) {
            return Node.DOCUMENT_POSITION_DISCONNECTED;
        }
    
        var children = reference.childNodes;
        var ret = recursivelyWalk(
            children,
            testNodeForComparePosition.bind(null, other)
        );
        if (ret) {
            return Node.DOCUMENT_POSITION_CONTAINED_BY +
                Node.DOCUMENT_POSITION_FOLLOWING;
        }
    
        var children = other.childNodes;
        var ret = recursivelyWalk(
            children, 
            testNodeForComparePosition.bind(null, reference)
        );
        if (ret) {
            return Node.DOCUMENT_POSITION_CONTAINS +
                Node.DOCUMENT_POSITION_PRECEDING;
        }
    
        var ret = recursivelyWalk(
            [referenceTop],
            identifyWhichIsFirst
        );
        if (ret === "other") {
            return Node.DOCUMENT_POSITION_PRECEDING;
        } else {
            return Node.DOCUMENT_POSITION_FOLLOWING;
        }
    }
    

    我自己写的。我认为这个实现是错误的,但这是我的其他一些代码中的错误。看起来很扎实。

    【讨论】:

    • 哦,Raynos,你真棒。感谢您回答我的问题
    • @TimDown 我没睡够。
    【解决方案2】:

    Raynos 的回答是一个好的开始,但不是开箱即用的。 Node.* 找不到,.bind 在 IE8 中不可用。

    下面是可在 Internet Explorer 8 中使用的代码:

    function recursivelyWalk(nodes, cb) {
        for (var i = 0, len = nodes.length; i < len; i++) {
            var node = nodes[i];
            var ret = cb(node);
            if (ret) {
                return ret;
            }
            if (node.childNodes && node.childNodes.length) {
                var ret = recursivelyWalk(node.childNodes, cb);
                if (ret) {
                    return ret;
                }
            }
        }
    }
    
    function testNodeForComparePosition(node, other) {
        if (node === other) {
            return true;
        }
    }
    
    var DOCUMENT_POSITION_DISCONNECTED = 1;
    var DOCUMENT_POSITION_PRECEDING = 2;
    var DOCUMENT_POSITION_FOLLOWING = 4;
    var DOCUMENT_POSITION_CONTAINS = 8;
    var DOCUMENT_POSITION_CONTAINED_BY = 16;
    
    function compareDocumentPosition(thisNode, other) {
        function identifyWhichIsFirst(node) {
            if (node === other) {
                return "other";
            } else if (node === reference) {
                return "reference";
            }
        }
    
        var reference = thisNode,
            referenceTop = thisNode,
            otherTop = other;
    
        if (this === other) {
            return 0;
        }
        while (referenceTop.parentNode) {
            referenceTop = referenceTop.parentNode;
        }
        while (otherTop.parentNode) {
            otherTop = otherTop.parentNode;
        }
    
        if (referenceTop !== otherTop) {
            return DOCUMENT_POSITION_DISCONNECTED;
        }
    
        var children = reference.childNodes;
        var ret = recursivelyWalk(
            children,
            function(p) {
                (function() {
                    var localOther = other;
                    return testNodeForComparePosition(localOther, p);
                })();
            }
        );
        if (ret) {
            return DOCUMENT_POSITION_CONTAINED_BY +
                DOCUMENT_POSITION_FOLLOWING;
        }
    
        var children = other.childNodes;
        var ret = recursivelyWalk(
            children,
            function(p) {
                (function() {
                    var localOther = reference;
                    return testNodeForComparePosition(localOther, p);
                })();
            }
        );
        if (ret) {
            return DOCUMENT_POSITION_CONTAINS +
                DOCUMENT_POSITION_PRECEDING;
        }
    
        var ret = recursivelyWalk(
            [referenceTop],
            identifyWhichIsFirst
        );
        if (ret === "other") {
            return DOCUMENT_POSITION_PRECEDING;
        } else {
            return DOCUMENT_POSITION_FOLLOWING;
        }
    }
    

    你这样称呼它:

    compareDocumentPosition(sourceElement, elementToTest)
    

    (就像打电话给sourceElement.compareDocumentPosition(elementToTest)

    【讨论】: