删除除白名单

时间:2017-04-08 14:08:24

标签: javascript dom

我们想要什么

  • 功能: purgeNodes(root, whitelist)

  • 操作:删除root的所有后代,但 whitelist(节点数组)[除了那些节点的后代 4月9日]。

  • 返回:Nothing,或删除的节点数组,或其他任何内容。

困难在哪里?

黑名单不是对白名单的简单否定。所以它并不像以下那么简单:

function purgeNodes(root, whitelist) {
    var all = [...root.querySelectorAll("*")];
    var blacklist = all.filter(n => ~whitelist.indexOf(n));
    blacklist.forEach(n => n.remove());
    return blacklist;
}

换句话说,all != [...blacklist, ...whitelist]

相反,all == [...blacklist, ...greylist, ...whitelist] ...

...其中greylist个节点是whitelist个节点的祖先,但不是他们的兄弟节点。

...其中greylist是一个隐式数组的祖先和whitelist个节点的后代(但不是他们的兄弟姐妹)应该像whitelist那样处理(即不得删除)。 4月9日

增加的复杂性是黑名单不应包含其他黑名单节点的后代节点以防止冗余删除。 (删除祖先会自动删除所有后代。)如果它确实包含它们,那就没关系了,如果这样可以大大简化代码。 4月8日

另一个地雷是某些whitelist元素是其他whitelist元素的祖先/后代的情况。也许这可以通过要求用户在将whitelist传递给purgeNodes()之前消除后代节点来避免这种情况。 4月9日

最后的话

有很多方法可以实现它,我很想看到每一个和任何一个。我不确定如何选择答案,但我可能会选择最受欢迎的答案,或者最旧的答案,或最佳代码。一个 JScript JQuery 答案将作为解决方案被选中,但看到它们很有意思,它们也应该获得赞成。我可能会也可能不会发布自己的答案。

1 个答案:

答案 0 :(得分:1)

正如评论和问题的更新中所表明的那样,有两种解释。删除没有列入白名单的子节点的节点,以及仅在没有列入白名单的祖先的情况下删除这些节点的节点。

我为每种情况提供单独的解决方案。

1。如果节点没有列入白名单的后代

,则会将其删除
function purgeNodes(root, whitelist) {
    const hash = new Set(whitelist); // for faster look-up
    const blacklist = [...root.querySelectorAll("*")].reverse().filter( node =>
        !node.children.length && !hash.has(node) && node.parentNode.removeChild(node)
    );
    return blacklist;
}

function purgeNodes(root, whitelist) {
    const hash = new Set(whitelist); // for faster look-up
    const blacklist = [...root.querySelectorAll("*")].reverse().filter( node =>
        !node.children.length && !hash.has(node) && node.parentNode.removeChild(node)
    );
    return blacklist;
}

purge.onclick = _ => purgeNodes(document.body, document.querySelectorAll('.white'));
div { margin-left: 10px; padding-left: 10px; border: 1px solid; background-color: white }
.white { background-color: pink }
<button id="purge">Purge</button>
<div id="a">a
    <div id="b" class="white">b
        <div id="c">c</div>
        <div id="d">d</div>
        <div id="e">e</div>
    </div>
    <div id="f">f
        <div id="g">g</div>
        <div id="h">h
            <div id="i">i
                <div id="j">j</div>
                <div id="k" class="white">k</div>
                <div id="l">l</div>
            </div>
        </div>
        <div id="m">m</div>
    </div>
    <div id="n">n
        <div id="o">o</div>
        <div id="p">p</div>
    </div>
</div>

reverse()调用对此算法至关重要。它依赖于querySelectorAll('*')将返回节点in document order w3.org(强调我的)的事实:

  

querySelectorAll()DocumentDocumentFragment接口上的Element方法必须返回包含所有匹配的NodeList节点的Element在上下文节点的子树中, 按文档顺序

另见MDN

  

...深度优先的预订遍历...

因此,如果以相反的顺序迭代它们,您将从叶子开始。如果删除该节点,则父节点可能成为叶子。如果该父级永远不会成为叶子,则意味着它具有白名单上的后代。因此,节点树从下往上进行修剪。

此算法仅将元素视为子元素,而不是文本节点和其他内容(注释,...)。因此,如果容器元素存活下来,后者将继续存在。

2。如果节点没有后代,也没有列入白名单的祖先

,则会删除节点

在这种情况下,递归的自顶向下算法更合适:

function purgeNodes(root, whitelist) {
    const hash = new Set(whitelist); // for faster look-up
    const recurse = node => hash.has(node) || [...node.children].filter(recurse)[0] 
                                           || !node.parentNode.removeChild(node);
    recurse(root);
}

function purgeNodes(root, whitelist) {
    const hash = new Set(whitelist); // for faster look-up
    const recurse = node => hash.has(node) || [...node.children].filter(recurse)[0] 
                                           || !node.parentNode.removeChild(node);
    recurse(root);
}

purge.onclick = _ => purgeNodes(document.body, document.querySelectorAll('.white'));
div { margin-left: 10px; padding-left: 10px; border: 1px solid; background-color: white }
.white { background-color: pink }
<button id="purge">Purge</button>
<div id="a">a
    <div id="b" class="white">b
        <div id="c">c</div>
        <div id="d">d</div>
        <div id="e">e</div>
    </div>
    <div id="f">f
        <div id="g">g</div>
        <div id="h">h
            <div id="i">i
                <div id="j">j</div>
                <div id="k" class="white">k</div>
                <div id="l">l</div>
            </div>
        </div>
        <div id="m">m</div>
    </div>
    <div id="n">n
        <div id="o">o</div>
        <div id="p">p</div>
    </div>
</div>

请注意不同的输出:节点 c,d e 未被删除,因为 b 已列入白名单。

此函数不会返回已删除的节点,只是一个值,指示在清除后root是否仍然有子节点:falsy / truthy值(不一定是boolean)。

请注意,!node.parentNode.removeChild(node)始终为false,因为removeChild将返回已删除的元素,并且其中的否定(!)始终为false。这是为了确保函数在删除作为参数传递的节点时返回false

[...node.children].filter(recurse)[0]将对每个子节点执行递归,并且只保留一个数组,其中该调用是真实的,即不应删除的子节点。通过引用[0],我们检查是否至少有一个这样的节点。我也可以使用.length.length>0,但[0]更短,并且当它存在时也具有真值,因为它是一个对象。