简体   繁体   中英

Javascript - remove child or parent elements from an array

I got a code to remove child/parent elements from a random array that might contain both a child and a parent element, for example:

<html>

    <body>
        <div id='1'>
            <div id='2'>
                <div id='3'>
                </div>
                <div id='4'>
                </div>
            </div>
        </div>
        <div id='5'>
            <div id='6'>
            </div>
        </div>
    </body>

</html>

arr = document.getElementsByTagName('div')
// arr: [<div#1>,<div#2>, <div#3>, <div#4>, <div#5>, <div#6>]

So from this example how can I extract the children:

// arr: [<div#3>, <div#4><div#6>]

Or extract the parents:

// arr: [<div#1>, <div#5>]

currently I'm using:

function isDescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}

function filterArray(arr, parent=true){
    newArr = [];
    arr.forEach((a)=>{
        bool = true

        if (parent){
            arr.forEach((b)=>{
                if (isDescendant(a, b)){
                    bool = false
                };
            });
        }
        else{
            arr.forEach((b)=>{
                if (isDescendant(b, a)){
                    bool = false
                };
            });            
        }

        if(bool){
            newArr.push(a)
        }
    });
    return newArr
};

But I'm pretty sure there could be a better solution, more efficient. Any idea for a better solution?

Arrays have a method called filter which lets you do just that; filter an array. To find if a node is a parent or child of another node, you can either use the contains -method (note that this might return true when checking if a node contains itself), or the more generic compareDocumentPosition -method.

 const nodes = Array.from(document.body.querySelectorAll("div")); //The most straight-forward way to find the parents, //filter out any nodes where no other node in the array contains it //(note the m !== n check, which prevents contains to return true for the same node): let parents = nodes.filter( n => !nodes.find( m => m !== n && m.contains(n) )); //Conversely, to find any child-nodes, invert the contains-check to find any nodes that does not contain any other node in the array: let children = nodes.filter( n => !nodes.find( m => m !== n && n.contains(m) )); console.log("approach 1:\\n", parents, "\\n", children); //Here is the same approach using compareDocumentPosition instead of contains: parents = nodes.filter( n => !nodes.find(m => m.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_CONTAINED_BY) ); children = nodes.filter( n => !nodes.find(m => n.compareDocumentPosition(m) & Node.DOCUMENT_POSITION_CONTAINED_BY) ) console.log("approach 2:\\n", parents, "\\n", children); //And finally, if you don't need the restriction of checking against //elements in the array, you can just see if the nodes have //the topmost parent/any children at all: const topElement = document.body; parents = nodes.filter( n => n.parentElement === topElement ); children = nodes.filter( n => !n.childElementCount ); console.log("approach 3:\\n", parents, "\\n", children); 
 <div id='1'> <div id='2'> <div id='3'> </div> <div id='4'> </div> </div> </div> <div id='5'> <div id='6'> </div> </div> 

A quick benchmark reveals the last method to be fastest (at least on my machine) (no surprise, it doesn't have to search the array multiple times), followed by the contains -version. The slowest is using compareDocumentPosition , but that is still faster than running filterArray to get the child array.

For children can convert collection to array and filter() using querySelector() which won't return anything when element isn't a parent

For outer parents can use Array#some() and node#contains() as filter()

 const arr = Array.from(document.querySelectorAll('div')); const innerChildren = arr.filter(el => !el.querySelector('*')); // or use same selector as master query const outerParents = arr.filter(e => !arr.some(el => el !== e && el.contains(e))); console.log(innerChildren) // div#3, div#4 & div#6 console.log(outerParents) // div#1, div#5 
 <div id='1'> <div id='2'> <div id='3'></div> <div id='4'></div> </div> </div> <div id='5'> <div id='6'></div> </div> 

Try following. It pick the right answers although not generic. Hope works for your case. Should be with less data loops under scene too.

const nodes = Array.from(document.body.querySelectorAll("div"));

const children = nodes.filter( node => 0 === node.querySelectorAll('div').length );
const parents = nodes.filter( node => 'DIV' !== node.parentNode.nodeName );

Here is JsFiddle: https://jsfiddle.net/3nhzvat9/

and another solution that should perform even better:

const nodes = document.body.querySelectorAll("div");
const children = [];
const parents = [];

nodes.forEach( node => {
   if(0  === node.querySelectorAll('div').length) {
      children.push(node);
   }
   if('DIV' !== node.parentNode.nodeName){
     parents.push(node);
   }
});

second JsFiddle: https://jsfiddle.net/3nhzvat9/1/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM