通过对象属性从数组中删除对象

188
var listToDelete = ['abc', 'efg'];

var arrayOfObjects = [{id:'abc',name:'oh'}, // delete me
                      {id:'efg',name:'em'}, // delete me
                      {id:'hij',name:'ge'}] // all that should remain

如何通过匹配对象属性来从数组中移除对象?

请只使用原生JavaScript。

我在使用splice时遇到了问题,因为每次删除后长度都会减少。 在克隆并对原始索引进行拼接的情况下仍然存在长度减少的问题。


2
很遗憾,目前没有一种标准的适当方法来从对象数组中删除项目。难怪有这么多第三方脚本。这是一个基本的事情。 - guitarlass
可能是根据对象属性删除数组元素的重复问题。 - vahdet
15个回答

186

我假设你使用了像这样的splice函数?

for (var i = 0; i < arrayOfObjects.length; i++) {
    var obj = arrayOfObjects[i];

    if (listToDelete.indexOf(obj.id) !== -1) {
        arrayOfObjects.splice(i, 1);
    }
}

修复错误的方法就是在下一次循环中将 i 减少,然后(倒序循环也是一个选项):

for (var i = 0; i < arrayOfObjects.length; i++) {
    var obj = arrayOfObjects[i];

    if (listToDelete.indexOf(obj.id) !== -1) {
        arrayOfObjects.splice(i, 1);
        <b>i--;</b>
    }
}

为了避免线性时间的删除操作,您可以在数组上写入您想要保留的数组元素:

var end = 0;

for (var i = 0; i < arrayOfObjects.length; i++) {
    var obj = arrayOfObjects[i];

    if (listToDelete.indexOf(obj.id) === -1) {
        arrayOfObjects[end++] = obj;
    }
}

arrayOfObjects.length = end;

为了避免在现代运行时中进行线性时间查找,您可以使用哈希集合:

const setToDelete = new Set(listToDelete);
let end = 0;

for (let i = 0; i < arrayOfObjects.length; i++) {
    const obj = arrayOfObjects[i];

    if (setToDelete.has(obj.id)) {
        arrayOfObjects[end++] = obj;
    }
}

arrayOfObjects.length = end;

可以用一个好的函数来封装:

const filterInPlace = (array, predicate) => {
    let end = 0;

    for (let i = 0; i < array.length; i++) {
        const obj = array[i];

        if (predicate(obj)) {
            array[end++] = obj;
        }
    }

    array.length = end;
};

const toDelete = new Set(['abc', 'efg']);

const arrayOfObjects = [{id: 'abc', name: 'oh'},
                        {id: 'efg', name: 'em'},
                        {id: 'hij', name: 'ge'}];

filterInPlace(arrayOfObjects, obj => !toDelete.has(obj.id));
console.log(arrayOfObjects);

如果你不需要原地操作,那就使用Array#filter

const toDelete = new Set(['abc', 'efg']);
const newArray = arrayOfObjects.filter(obj => !toDelete.has(obj.id));

134
你可以通过其中一个属性来删除项目,而无需使用任何第三方库,例如这样:
var removeIndex = array.map(item => item.id).indexOf("abc");

~removeIndex && array.splice(removeIndex, 1);

29
如果您被最后一行的波浪符号搞糊涂了,您可能会发现这更清晰一些: (removeIndex >= 0)&& array.splice(removeIndex, 1); - wojjas
1
这个答案比选定的更快、更优雅: https://jsben.ch/ld9op - Zack Lee
@ZackLee:对于5/10个元素,肯定可以更快。这里是50/100的链接:https://jsben.ch/55g97 - Ry-
你可以跳过一些元素的集合构建,然后速度会更快,即使你缓存了ID数组。此外,这两种方法对待重复项的方式不同。不确定这是否重要。 - Ry-

83

使用Lodash/Underscore:

如果您想要修改现有的数组自身,则必须使用splice。这是一种更好/易读的方法,使用underscore/lodash的findWhere

var items= [{id:'abc',name:'oh'}, // delete me
                  {id:'efg',name:'em'},
                  {id:'hij',name:'ge'}];

items.splice(_.indexOf(items, _.findWhere(items, { id : "abc"})), 1);

使用ES5或更高版本

(不需要lodash/underscore)

在ES5及以上版本中,我们有数组的findIndex方法,所以不需要使用lodash/underscore就可以更容易地实现。

items.splice(items.findIndex(function(i){
    return i.id === "abc";
}), 1);

几乎所有现代浏览器都支持ES5。

关于findIndex以及它的浏览器兼容性


1
很好。我将把它添加到我的工具箱中。 - abyrne85
另外我想要的是,每个数组对象都有单独的按钮。如果我想要删除数组中的特定对象,只需点击相应的按钮,然后将其移动到另一个辅助数组中。如何实现呢?我已经使用了AngularJS的ng-repeat来生成这些项。你能帮我吗? - Thilak Raj
相同操作,当点击时获取项目的id(或key),使用finWhere查找它,使用splice删除它,并将其推送到新数组中。 var toBeRemoved = .findWhere(items, { id : "abc"}); items.splice(.indexOf(toBeRemoved), 1); newArray.push(toBeRemoved); - Rahul R.
11
我很喜欢这个 ES5 的建议,因为它甚至可以用更短的方式编写:items.splice(items.findIndex(i => i.id === "abc"), 1) - bramchi
5
请小心。这不会删除数组中所有具有相同“id”的对象。 - Jaseem Abbas
显示剩余6条评论

40

通过给定数组中的ID删除一个对象;

const hero = [{'id' : 1, 'name' : 'hero1'}, {'id': 2, 'name' : 'hero2'}];
//remove hero1
const updatedHero = hero.filter(item => item.id !== 1);

filter()会在找到第一个元素后继续过滤。 - ccpizza

34

findIndex 只适用于现代浏览器:

var myArr = [{id:'a'},{id:'myid'},{id:'c'}];
var index = myArr.findIndex(function(o){
  return o.id === 'myid';
})
if (index !== -1) myArr.splice(index, 1);

我喜欢这个现代的答案,但是你的代码没有捕捉到当index=-1的情况。 - rmcsharry
2
与其他答案相比,非常简单且完全有效。 - arun
简单而强大的答案 - Shelly

12

使用Set和ES5过滤器检查这个。

  let result = arrayOfObjects.filter( el => (-1 == listToDelete.indexOf(el.id)) );
  console.log(result);

这里是 JsFiddle: https://jsfiddle.net/jsq0a0p1/1/


9
如果您只想将其从现有数组中删除而不创建新数组,请尝试以下操作:
var items = [{Id: 1},{Id: 2},{Id: 3}];
items.splice(_.indexOf(items, _.find(items, function (item) { return item.Id === 2; })), 1);

8
请注意,这个答案需要使用 underscore 库。 - JoshuaDavid
2
我不建议这样做,因为如果找不到该元素,_.indexOf将返回-1 => items.splice(-1,1)。 - user1441287

7

通过将i递减来反向循环,以避免此问题:

for (var i = arrayOfObjects.length - 1; i >= 0; i--) {
    var obj = arrayOfObjects[i];

    if (listToDelete.indexOf(obj.id) !== -1) {
        arrayOfObjects.splice(i, 1);
    }
}

或者使用filter

var newArray = arrayOfObjects.filter(function(obj) {
    return listToDelete.indexOf(obj.id) === -1;
});

4

请使用纯粹的JavaScript。

作为另一种解决方案,更加“函数式”,适用于ECMAScript 5,您可以使用以下代码:

var listToDelete = ['abc', 'efg'];
var arrayOfObjects = [{id:'abc',name:'oh'}, // delete me
                      {id:'efg',name:'em'}, // delete me
                      {id:'hij',name:'ge'}]; // all that should remain

arrayOfObjects.reduceRight(function(acc, obj, idx) {
    if (listToDelete.indexOf(obj.id) > -1)
        arrayOfObjects.splice(idx,1);
}, 0); // initial value set to avoid issues with the first item and
       // when the array is empty.

console.log(arrayOfObjects);
[ { id: 'hij', name: 'ge' } ]

根据ECMA-262中'Array.prototype.reduceRight'的定义

reduceRight不会直接改变被调用的对象,但回调函数的调用可能会改变该对象。

因此,这是使用 reduceRight 的有效方法。

1
var arrayOfObjects = [{id:'abc',name:'oh'}, // delete me
                      {id:'efg',name:'em'}, // delete me
                      {id:'hij',name:'ge'}] // all that should remain

根据您的回答,操作如下。当您点击某个特定对象时,在参数中发送索引以执行“删除”函数。这段简单的代码将完美运行。
function deleteme(i){
    if (i > -1) {
      arrayOfObjects.splice(i, 1);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接