Javascript 使用queryselectorall、jquery、getelementsbyid、getelementsbyclassname从dom中删除元素

Javascript 使用queryselectorall、jquery、getelementsbyid、getelementsbyclassname从dom中删除元素,javascript,Javascript,我正在为一个库编写一个函数,它接受一个元素对象并将其从DOM中删除。我可以很好地工作,但是想知道是否有一种方法可以为循环执行单个?我发现NodeLists和HTMLCollections不能使用相同的for循环,因此我构建了一个数组,然后删除具有相同循环的元素 _remove = function(elem) { if(!elem) { return false; } // getElementById if(!elem.length) {

我正在为一个库编写一个函数,它接受一个元素对象并将其从DOM中删除。我可以很好地工作,但是想知道是否有一种方法可以为循环执行单个
?我发现
NodeLists
HTMLCollections
不能使用相同的for循环,因此我构建了一个数组,然后删除具有相同循环的元素

   _remove = function(elem) {
      if(!elem) { return false; }
      // getElementById
      if(!elem.length) {
        if(elem.parentNode) {
          elem.parentNode.removeChild(elem);
        }
      // querySelectorAll, jquery, getElementsByClassName, getElementsByTagName
      } else {
        var elems = [];
        for(var j = 0; j<elem.length; j++) {
          if(elem[j]) {
            elems.push(elem[j]);
          }
        }
        for(var i=0; i<elems.length; i++) {
          if(elems[i].parentNode) {
            elems[i].parentNode.removeChild(elems[i]);
          }
        }
        return false;
      }
    }

其中一些函数返回一个活动节点列表,这意味着当您更改DOM时,集合会立即更改以反映这一点。这就是为什么正常的
for
循环不起作用的原因:当删除
元素[0]
时,集合中的所有其他项都向下移动。然后,当增加索引时,跳过新的
元素[0]

解决此问题的最简单方法是从高端向下循环,而不是从0向上循环

for (var j = elem.length-1; j >= 0; j--) {
    if (elem[j].parentNode) {
        elem[j].parentNode.removeChild(elem[j]);
    }
}

出于以下原因,我建议使用更健壮的实现:

  • 如果是传入的jQuery对象,那么应该调用jQuery
    .remove()
    ,因为如果不让jQuery删除jQuery操作(如
    .data()
    .on()
    等)所使用的DOM元素,jQuery可能会泄漏数据和事件侦听器的内存

  • 仅仅做
    if(elem.length)
    是不够的,因为它仍然可以是
    nodeList
    .length
    0
    的数组。我将其更改为test,以查看该属性是否确实存在。仅供参考,如果您想要一个更健壮的测试来查看它是否是一个实际的单个DOM节点,那么有一个健壮的函数来实现这一点,尽管我不确定这里是否需要这样做

  • 为了防止动态节点列表从您下方变出,或者子节点和父节点都在列表中(您不想在子节点之前删除父节点),最好从后向前迭代节点列表(您不必制作副本)

  • 如果这只是一个普通的DOM元素数组,不一定按文档顺序排列,并且列表中有父元素/子元素,或者有人向您传递了一个包含任何旧DOM元素的数组,那么您可能会在
    .removeChild()上遇到异常
    操作,因此最好捕获可能发生的任何异常,以便操作始终可以在整个列表中完成,即使列表中有不正确的元素

  • 您的代码没有一致的返回值。它有一条路径没有返回任何东西。我将其更改为返回
    false
    ,如果
    elem
    中没有任何内容是错误的,则返回
    true
    。显然,您可以根据需要修改它,但是如果您想要有一个有意义的返回值,那么所有代码路径都应该有一个返回值(如果它是单个DOM节点,则没有)

  • 推荐代码:

    // _remove(elem) - remove one or more DOM nodes from their DOM hierarchy
    // elem can be an Array of DOM nodes or a pseudo-array like a nodeList 
    // or elem can be a single DOM element
    
    function _remove(elem) {
        if (!elem) {
            return false;
        } else if (typeof elem.jquery === "string" && typeof elem.remove === "function") {
            // if a jQuery object, it should be removed with jQuery 
            // so jQuery data and event listener stuff will get cleaned up
            // there are more comprehensive ways to check for a jQuery object,
            // but those are probably not needed here
            elem.remove();
        } else if (elem.nodeType) {
            // single DOM node
            // could also do more comprehensive test to see if it's really a DOM node, 
            // but probably not needed
            if (elem.parentNode) {
                elem.parentNode.removeChild(elem);
            }
        } else if (typeof elem.length === "number") {
            // array or pseudo-array here
            // querySelectorAll, getElementsByClassName, getElementsByTagName or 
            // an array of DOM elements assembled by any other code
            // iterate backwards so if it is a dynamic nodeList, then children will be
            // be removed before parents and any nodeList changes will happen after our
            // iteration point
            for (var i = elem.length - 1; i >= 0; i--) {
                // catch errors in case anything has already been deleted
                // from a prior parent delete so we don't abort early
                try {
                    elem[i].parentNode.removeChild(elem[i]);
                } catch(e) { }
            }
        } else {
            // don't know what was passed here - not something we were expecting
            return false;
        }
        return true;
    }
    

    以相反的方式迭代,两种类型的列表都会涉及到你。你能举个例子吗?我使用了这两种类型的循环,因为当节点列表单独处理第二个循环时,HTMLCollections会在循环运行时删除减少I的项…因此集合中有一半的项没有被删除开始时,将
    I
    作为最后一个索引,而不是第一个索引,和递减而不是递增。
    gEBTN
    gEBCN
    返回实时列表,但
    qSA
    不返回。不删除父元素,这只是本地js删除元素的方式,选择其父元素,然后删除parentNode中的childtypo,因此不允许我编辑,但除此之外效果很好,谢谢@jfriend00:我不认为这会是一个问题,因为文档顺序决定了深度优先顺序,所以反过来说,孩子总是在父母之前被移除。如果我错了,你能举个例子吗?@jfriend00这会是个什么问题?不过我认为您需要处理重复项。您应该删除elem.length==“undefined”
    部分的
    类型,或者以其他方式对其进行返工。某些元素既是元素又是集合,例如
    元素分别具有
    .length
    和引用其输入和选项的数字索引。因此,现在它将进入下一个条件,并删除列表项而不是元素……但是为了安全起见,使用jQuery的
    .remove()
    是一个很好的方法。@斜视-关于
    .length
    的一个很好的方法。我删除了该检查,只检查
    .nodeType
    属性。
    // _remove(elem) - remove one or more DOM nodes from their DOM hierarchy
    // elem can be an Array of DOM nodes or a pseudo-array like a nodeList 
    // or elem can be a single DOM element
    
    function _remove(elem) {
        if (!elem) {
            return false;
        } else if (typeof elem.jquery === "string" && typeof elem.remove === "function") {
            // if a jQuery object, it should be removed with jQuery 
            // so jQuery data and event listener stuff will get cleaned up
            // there are more comprehensive ways to check for a jQuery object,
            // but those are probably not needed here
            elem.remove();
        } else if (elem.nodeType) {
            // single DOM node
            // could also do more comprehensive test to see if it's really a DOM node, 
            // but probably not needed
            if (elem.parentNode) {
                elem.parentNode.removeChild(elem);
            }
        } else if (typeof elem.length === "number") {
            // array or pseudo-array here
            // querySelectorAll, getElementsByClassName, getElementsByTagName or 
            // an array of DOM elements assembled by any other code
            // iterate backwards so if it is a dynamic nodeList, then children will be
            // be removed before parents and any nodeList changes will happen after our
            // iteration point
            for (var i = elem.length - 1; i >= 0; i--) {
                // catch errors in case anything has already been deleted
                // from a prior parent delete so we don't abort early
                try {
                    elem[i].parentNode.removeChild(elem[i]);
                } catch(e) { }
            }
        } else {
            // don't know what was passed here - not something we were expecting
            return false;
        }
        return true;
    }