JavaScript中删除数组元素 - delete vs splice

1450

使用delete操作符和使用Array.splice方法在数组元素上的区别是什么?

例如:

myArray = ['a', 'b', 'c', 'd'];

delete myArray[1];
//  or
myArray.splice (1, 1);

如果我可以像操作对象一样删除数组元素,那为什么还需要splice方法呢?


3
关于在循环中使用 .splice,请参考这个问题:在JavaScript中从数组中删除元素 - Rob W
6
是的,但是这个回答是在之后的一天发布的,并且得到了更多的投票 - 我会说它写得更好,这就是原因。 - Camilo Martin
@andynormancx — 看起来这不是完全重复的问题。你链接的那个问题基本上是在问为什么从数组中删除一个元素(从而使其变得稀疏)不会减少它的长度。而这个问题则是在询问deleteArray.prototype.splice之间的区别。 - chharvey
@chharvey同意,但更重要的是,这个问题怎么可能在9年前发布!让我感到老了,现在又回来评论它。 - andynormancx
《JavaScript:前20年》提到:“JavaScript 1.1添加了delete、typeof和void运算符。在JavaScript 1.1中,delete运算符仅将其变量或对象属性操作数设置为null值。”并且splice被添加到1.2版本中,“数组push、pop、shift、unshift和splice直接模拟了同名的Perl数组函数。” - Qiulang
29个回答

6
你可以使用类似以下的方式:

var my_array = [1,2,3,4,5,6];
delete my_array[4];
console.log(my_array.filter(function(a){return typeof a !== 'undefined';})); // [1,2,3,4,6]


2
如果你来自函数式编程背景,那么这种方法比 slice 更直观(尽管可能不太高效)。 - jethro

6
如果您想逐个遍历一个大型数组并有选择地删除元素,每次调用splice()都会很昂贵,因为splice()每次都要重新索引后续元素。由于JavaScript中的数组是关联数组,删除单个元素然后在之后重新索引数组更有效率。
您可以通过构建新数组来实现,例如:
function reindexArray( array )
{
       var result = [];
        for( var key in array )
                result.push( array[key] );
        return result;
};

但我认为您无法修改原始数组中的键值,这样更有效率-看起来您可能必须创建一个新数组。
请注意,您不需要检查“undefined”条目,因为它们实际上不存在,并且for循环不会返回它们。它是数组打印的副产品,显示它们未定义。它们似乎不存在于内存中。
如果您能使用类似slice()的东西,那将更快,但它不重新索引。有人知道更好的方法吗?
实际上,您可能可以按以下方式直接执行此操作,从性能方面来看可能更有效率:
reindexArray : function( array )
{
    var index = 0;                          // The index where the element should be
    for( var key in array )                 // Iterate the array
    {
        if( parseInt( key ) !== index )     // If the element is out of sequence
        {
            array[index] = array[key];      // Move it to the correct, earlier position in the array
            ++index;                        // Update the index
        }
    }

    array.splice( index );  // Remove any remaining elements (These will be duplicates of earlier items)
},

我认为++index;需要在“if”之外发生。进行编辑。 - mishal153

6
delete 的作用就像一个非现实的场景,它只是移除了该项,但数组的长度保持不变:
来自 node 终端的示例:
> var arr = ["a","b","c","d"];
> delete arr[2]
true
> arr
[ 'a', 'b', , 'd', 'e' ]

这里有一个使用 slice() 函数按索引从数组中删除元素的方法,它将数组作为第一个参数,要删除的成员的索引作为第二个参数。正如您所看到的,它实际上删除了数组的成员,并使数组长度减少了1。

function(arr,arrIndex){
    return arr.slice(0,arrIndex).concat(arr.slice(arrIndex + 1));
}

上述函数的作用是将索引之前和之后的所有成员连接在一起,并返回结果。
以下是使用上述函数作为节点模块的示例,查看终端会有帮助:
> var arr = ["a","b","c","d"]
> arr
[ 'a', 'b', 'c', 'd' ]
> arr.length
4 
> var arrayRemoveIndex = require("./lib/array_remove_index");
> var newArray = arrayRemoveIndex(arr,arr.indexOf('c'))
> newArray
[ 'a', 'b', 'd' ] // c ya later
> newArray.length
3

请注意,这种方法在包含重复元素的数组中不起作用,因为 indexOf("c") 仅获取第一个匹配项,并且只会删除它找到的第一个 "c"。

4

性能

已经有许多有关功能差异的好答案了 - 所以在这里我想集中讨论性能。今天(2020.06.25),我对Chrome 83.0、Safari 13.1和Firefox 77.0进行了测试,包括问题中提到的解决方案以及选择的答案。

结论

  • the splice (B) solution is fast for small and big arrays
  • the delete (A) solution is fastest for big and medium fast for small arrays
  • the filter (E) solution is fastest on Chrome and Firefox for small arrays (but slowest on Safari, and slow for big arrays)
  • solution D is quite slow
  • solution C not works for big arrays in Chrome and Safari
    function C(arr, idx) {
      var rest = arr.slice(idx + 1 || arr.length);
      arr.length = idx < 0 ? arr.length + idx : idx;
      arr.push.apply(arr, rest);
      return arr;
    }
    
    
    // Crash test
    
    let arr = [...'abcdefghij'.repeat(100000)]; // 1M elements
    
    try {
     C(arr,1)
    } catch(e) {console.error(e.message)}

enter image description here

详情

我会对解决方案进行以下测试 A B C D E (我的)

  • 对于小数组(4个元素) - 您可以运行测试HERE
  • 对于大数组(1M个元素) - 您可以运行测试HERE

function A(arr, idx) {
  delete arr[idx];
  return arr;
}

function B(arr, idx) {
  arr.splice(idx,1);
  return arr;
}

function C(arr, idx) {
  var rest = arr.slice(idx + 1 || arr.length);
  arr.length = idx < 0 ? arr.length + idx : idx;
  arr.push.apply(arr, rest);
  return arr;
}

function D(arr,idx){
    return arr.slice(0,idx).concat(arr.slice(idx + 1));
}

function E(arr,idx) {
  return arr.filter((a,i) => i !== idx);
}

myArray = ['a', 'b', 'c', 'd'];

[A,B,C,D,E].map(f => console.log(`${f.name} ${JSON.stringify(f([...myArray],1))}`));
This snippet only presents used solutions

Chrome的示例结果

enter image description here


3
为什么不直接过滤?我认为这是在 JavaScript 中考虑数组的最明确的方法。
myArray = myArray.filter(function(item){
    return item.anProperty != whoShouldBeDeleted
});

一个包含数百个(甚至数千个)记录的数组,如果只需要删除其中3个记录,该怎么办? - Joel Hernandez
好主意。我使用这种方法来处理长度不超过200或300的稀疏数组,效果非常好。但是,如果你需要管理更大的数组,我认为像Underline这样的库应该更优先考虑。 - Asqan
1
然而,对我来说,它似乎比 deleteslice 更好。 - Juneyoung Oh

3
他们是不同的东西,有不同的用途。 splice 是专门针对数组的,当用于删除时,会从数组中移除条目,并将所有先前的条目上移以填补空缺。(它也可以用于插入条目,或同时进行插入和删除。)splice 将改变数组的 length(假设它不是一个无操作调用:theArray.splice(x, 0))。 delete并不是只适用于数组,它被设计用于对象:它会从你使用的对象中删除一个属性(键值对)。它之所以适用于数组,是因为在JavaScript中,标准(例如非类型化)数组根本不是数组*,它们是带有特殊处理某些属性的对象,例如那些名为“数组索引”的属性(这些属性被定义为字符串名称“...其中数值 i 在范围+0 ≤ i < 2^32-1内”)和length。当您使用delete删除数组条目时,它只会删除该条目;它不会移动其后面的其他条目来填补空缺,因此数组变得“稀疏”(完全丢失了一些条目)。它对length没有影响。
这个问题的一些当前答案错误地陈述了使用delete会“将条目设置为undefined”。这是不正确的。它会完全删除该条目(属性),留下一个空隙。
让我们使用一些代码来说明差异:

console.log("Using `splice`:");
var a = ["a", "b", "c", "d", "e"];
console.log(a.length);            // 5
a.splice(0, 1);
console.log(a.length);            // 4
console.log(a[0]);                // "b"

console.log("Using `delete`");
var a = ["a", "b", "c", "d", "e"];
console.log(a.length);            // 5
delete a[0];
console.log(a.length);            // still 5
console.log(a[0]);                // undefined
console.log("0" in a);            // false
console.log(a.hasOwnProperty(0)); // false

console.log("Setting to `undefined`");
var a = ["a", "b", "c", "d", "e"];
console.log(a.length);            // 5
a[0] = undefined;
console.log(a.length);            // still 5
console.log(a[0]);                // undefined
console.log("0" in a);            // true
console.log(a.hasOwnProperty(0)); // true


* (这是我的不起眼的小博客上的一篇文章)


我刚看到了你的文章,其中提到JavaScript中的数组实际上并不是真正的数组,基本上与lua数组相同。 - Qiulang

3

我有两种方法。

简单的方法:

arr = arr.splice(index,1)

第二个:

arr = arr.filter((v,i)=>i!==index)

第二种方法的优点是你可以移除一个值(不仅仅是像大多数情况下只移除第一个实例)。
arr = arr.filter((v,i)=>v!==value)

3

其他人已经正确地将deletesplice进行了比较。

另一个有趣的比较是deleteundefined之间的区别:被删除的数组项使用的内存比只设置为undefined的数组项少;

例如,这段代码将无法完成:

let y = 1;
let ary = [];
console.log("Fatal Error Coming Soon");
while (y < 4294967295)
{
    ary.push(y);
    ary[y] = undefined;
    y += 1;
}
console(ary.length);

它产生了这个错误:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory.

正如您所看到的, undefined 实际上占用了堆内存。

然而,如果您也删除 ary-item(而不仅仅将其设置为 undefined ),代码将慢慢完成:

let x = 1;
let ary = [];
console.log("This will take a while, but it will eventually finish successfully.");
while (x < 4294967295)
{
    ary.push(x);
    ary[x] = undefined;
    delete ary[x];
    x += 1;
}
console.log(`Success, array-length: ${ary.length}.`);

这些是极端的例子,但它们强调了一个关于 delete 的观点,我没有在任何地方看到过提到。


2
function remove_array_value(array, value) {
    var index = array.indexOf(value);
    if (index >= 0) {
        array.splice(index, 1);
        reindex_array(array);
    }
}
function reindex_array(array) {
   var result = [];
    for (var key in array) {
        result.push(array[key]);
    }
    return result;
}

例子:

var example_arr = ['apple', 'banana', 'lemon'];   // length = 3
remove_array_value(example_arr, 'banana');

香蕉已被删除,数组长度为2。


2

目前有两种方法可以实现这个:

  1. 使用 splice()

    arrayObject.splice(index, 1);

  2. 使用 delete

    delete arrayObject[index];

但是我建议在数组对象中使用splice,在对象属性中使用delete,因为delete不会更新数组的长度。


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