如何在特定索引处将项目插入数组中?

4211
我正在寻找一种JavaScript数组插入方法,类似于:
arr.insert(index, item)

最好使用jQuery,但目前任何JavaScript实现都可以。

151
请注意,jQuery是一个DOM和事件操作的库,而不是一种独立的编程语言。它与数组操作无关。 - Domino
36
https://api.jquery.com/jQuery.inArray/与DOM或事件无关。jQuery已经发展成为一个混合工具包,用于浏览器JS开发,导致人们期望它拥有一切方法。 - Tim
8
@Tim,但它仍然不是一种独立的语言(在SO上仍然有一些类似“如何在jQuery中求两个数字之和”的问题)。 - Victor
10
不,而且永远不会再是。jQuery曾经很有用且相关,但它已经过时了。 - Tim
我很烦恼这个问题的标题和页面标题不同(jQuery vs. JS)。在搜索结果中显示不同。 - Andrew
32个回答

6685

您想要在原生数组对象上使用 splice 函数。

arr.splice(index, 0, item); 将会在指定的 index 处将 item 插入到 arr 中(首先删除 0 个元素,也就是仅插入)。

在这个示例中,我们将创建一个数组,并将一个元素添加到索引为 2 的位置:

var arr = [];
arr[0] = "Jani";
arr[1] = "Hege";
arr[2] = "Stale";
arr[3] = "Kai Jim";
arr[4] = "Borge";

console.log(arr.join()); // Jani,Hege,Stale,Kai Jim,Borge
arr.splice(2, 0, "Lene");
console.log(arr.join()); // Jani,Hege,Lene,Stale,Kai Jim,Borge


260
谢谢,我原以为问这个问题会感到很蠢,但现在我知道答案了,我不觉得自己蠢了!为什么他们决定用“splice”这个词来表示相同功能的更常见的可搜索术语呢?! - tags2k
109
因为这个函数不仅仅是插入项目,而且它的名称已经在Perl中确定了。 - Christoph
18
这是一个关于JavaScript中预定义核心对象之一——数组对象的文档。它介绍了如何创建和操作数组以及使用数组相关的方法。需要注意的是,文档中的代码示例均为英文版,但这并不影响理解与操作。 - Dingo
76
Splice 可以 插入元素,但并不总是这样。例如:arr.splice(2,3)会从索引2开始删除3个元素,如果没有传递第三个到第n个参数,则不会插入任何元素。因此,insert() 这个名称也不太准确。 - EBarr
23
我认为术语“splice”有意义。Splice指的是连接或联结,也可以表示改变。您有一个已经“改变”的已建立的数组,这可能涉及添加或删除元素。您需要指定要从哪里开始更改数组,然后指定要删除多少旧项(如果有)以及最后,可选地添加新元素的列表。当然,“splice”也是一个很棒的科幻术语。 - Jakub Keller
显示剩余18条评论

417

你可以通过以下方式实现 Array.insert 方法:

Array.prototype.insert = function ( index, ...items ) {
    this.splice( index, 0, ...items );
};

那么你可以像这样使用它:

var arr = [ 'A', 'B', 'E' ];
arr.insert(2, 'C', 'D');

// => arr == [ 'A', 'B', 'C', 'D', 'E' ]

17
为了插入多个元素,你可以使用Array.prototype.insert = function (index, items) { this.splice.apply(this, [index, 0].concat(items)); } - Ryan Smith
10
将内容添加到数组的问题在于,当您执行 for(i in arr) {...} 时,该函数将显示为元素。 - rep_movsd
27
请记住,不建议扩展原生类型,因为它可能会干扰其他代码或未来的功能。 - marsze
100
不要改变你没有所有权的物体。 - Luis Cabrera Benito
38
不要修改原型。 - satya164
显示剩余6条评论

302

除了splice外,您可以使用此方法,它不会更改原始数组,但它将创建一个具有添加的项目的新数组。当需要避免突变时,这很有用。我在这里使用ES6扩展运算符。

const items = [1, 2, 3, 4, 5]

const insert = (arr, index, newItem) => [
  // part of the array before the specified index
  ...arr.slice(0, index),
  // inserted item
  newItem,
  // part of the array after the specified index
  ...arr.slice(index)
]

const result = insert(items, 1, 10)

console.log(result)
// [1, 10, 2, 3, 4, 5]

通过调整函数,使用rest运算符来添加新项目,然后在返回的结果中将其展开,可以使用此方法添加多个项目:

const items = [1, 2, 3, 4, 5]

const insert = (arr, index, ...newItems) => [
  // part of the array before the specified index
  ...arr.slice(0, index),
  // inserted items
  ...newItems,
  // part of the array after the specified index
  ...arr.slice(index)
]

const result = insert(items, 1, 10, 20)

console.log(result)
// [1, 10, 20, 2, 3, 4, 5]


3
这是一个好的、安全的方法吗?我问这个问题是因为这种方法看起来非常优雅和简洁,但其他答案都没有涉及到这一点。大多数答案都修改了原型对象! - anotherDev
10
可能是因为大多数答案都是ES6之前的,但从我的经验来看,这种方法现在非常普遍。 - gafi
2
这种方法总是比splice慢且更糟。不要被漂亮的语法或其他误导开发人员(*咳嗽* gafi *咳嗽*)的流行所迷惑。分配一个全新的数组并丢弃旧数组比修改原始数组要慢得多。如果需要副本,请在splice()之前调用slice()。永远不要为此类琐事使用ES6,因为可以使用其他API更好、更清晰地完成。 - Jack G
5
你应该解释为什么需要避免使用突变。它是否会使程序运行变慢?如果有影响,那么会慢多少?这样的影响是否值得额外的麻烦? - Daniele Testa
4
@JackG 别忘了 slice() 会分配一个新的数组,而且 splice() 也会分配一个新的数组,因为它返回被移除的项组成的数组(在这种情况下,是一个空数组)。对这些情况进行基准测试表明,splice() 更快(大约快30%),但我们每秒执行数百万次操作,所以除非你的应用程序在紧密循环中执行了 很多 这样的操作,否则不会有任何差异。 - nickf
@nickf 如果你正在剪切少量的项目(特别是零个项目),那么splice分配的小数组将成为年轻代的一部分,这是几乎零成本的内存。相比之下,复制大数组不会使用新空间,而是使用特殊的大对象空间,如果数组足够大,甚至可能调用系统调用来分配更多内存。只有在剪切掉非常大的数组的大部分时,splice才可能变慢。 - Jack G

90

自定义数组插入方法

1. 支持多个参数和链式调用

/* Syntax:
   array.insert(index, value1, value2, ..., valueN) */

Array.prototype.insert = function(index) {
    this.splice.apply(this, [index, 0].concat(
        Array.prototype.slice.call(arguments, 1)));
    return this;
};

它可以像本地的 splice 方法一样插入多个元素,并支持链式操作:

["a", "b", "c", "d"].insert(2, "X", "Y", "Z").slice(1, 6);
// ["b", "X", "Y", "Z", "c"]

2. 支持数组类型参数的合并和链接

/* Syntax:
   array.insert(index, value1, value2, ..., valueN) */

Array.prototype.insert = function(index) {
    index = Math.min(index, this.length);
    arguments.length > 1
        && this.splice.apply(this, [index, 0].concat([].pop.call(arguments)))
        && this.insert.apply(this, arguments);
    return this;
};

它可以将参数中的数组与给定的数组合并,并支持链接操作:
["a", "b", "c", "d"].insert(2, "V", ["W", "X", "Y"], "Z").join("-");
// "a-b-V-W-X-Y-Z-c-d"

演示: http://jsfiddle.net/UPphH/


有没有一种简洁的方法,使得这个版本在发现参数中有一个数组时也可以将其合并? - Nolo
我不理解第一个结果["b", "X", "Y", "Z", "c"]。为什么没有包括"d"?在我的理解中,如果你把slice()的第二个参数设置为6,并且从指定的索引开始有6个元素,那么你应该得到返回值中的所有6个元素。(文档中称其为howMany参数。)https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice - Alexis Wilke
实际上,如果我使用3或更高的索引,在输出中什么都没有(情况1,火狐浏览器)["a", "b", "c", "d"].insert(2, "X", "Y", "Z").slice(3, 3); => [ ] - Alexis Wilke
@AlexisWilke 在第一个例子中,我使用了 slice 方法而不是你在评论中提到的 spliceslice 的第二个参数(名为 end)是“从哪里结束提取”的基于零的索引。slice 提取直到但不包括 end。因此,在 insert 之后,您有 ["a", "b", "X", "Y", "Z", "c", "d"],从中 slice 提取从 "b""d" 但不包括 "d" 的索引为 16 的元素。这有意义吗? - VisioN
由于某些原因,当我尝试使用insert2时,会出现“期望1个参数,但得到2个”异常。 - Corné
显示剩余4条评论

85
使用Array.prototype.splice()是一种优雅的方法来实现它。

const numbers = ['one', 'two', 'four', 'five']
numbers.splice(2, 0, 'three');

console.log(numbers)

了解更多关于Array.prototype.splice的信息


1
如上所述,使用原型将向数组中添加一个额外的项,因此循环将无法按预期工作。 - Travis Heeter

56

如果你想一次性插入多个元素到一个数组中,请看一下这个Stack Overflow的答案:A better way to splice an array into an array in javascript

以下是两个示例函数:

function insertAt(array, index) {
    var arrayToInsert = Array.prototype.splice.apply(arguments, [2]);
    return insertArrayAt(array, index, arrayToInsert);
}

function insertArrayAt(array, index, arrayToInsert) {
    Array.prototype.splice.apply(array, [index, 0].concat(arrayToInsert));
    return array;
}

最后附上一个 jsFiddle,你可以自己查看:http://jsfiddle.net/luisperezphd/Wc8aS/

以下是如何使用这些函数:

// if you want to insert specific values whether constants or variables:
insertAt(arr, 1, "x", "y", "z");

// OR if you have an array:
var arrToInsert = ["x", "y", "z"];
insertArrayAt(arr, 1, arrToInsert);

2
insertAt() 调用 insertArrayAt() 一次创建单元素 arrayToInsert 是否更好?这样可以避免重复相同的代码。 - Matt Sach
1
这是使用“apply”的绝佳示例。 - CRice
我在这个方法中添加了一个removeCount参数,以利用splice的能力,在该索引处同时删除项目:Array.prototype.splice.apply(array, [index, removeCount || 0].concat(arrayToInsert)); - CRice
非常感谢,我会在qml中使用它。 - S At

46

你可以使用splice()来实现

splice()方法通常在添加元素时接收三个参数:

  1. 要添加项目的数组的索引
  2. 要删除的项目数,在本例中为0
  3. 要添加的元素

let array = ['item 1', 'item 2', 'item 3']
let insertAtIndex = 0
let itemsToRemove = 0
    
array.splice(insertAtIndex, itemsToRemove, 'insert this string at index 0')

console.log(array)


2
下次请仔细阅读答案,这个解决方案已经给出了。你只是在重复信息。 - vdegenne
7
当我发布这个答案时,我认为其他答案可能需要更好地解释。赞同票的数量清楚地表明该答案对某些人很有用。 - Gass
2
是的,解释第二个参数的含义尤其有帮助。 - djna

44

解决方案和性能

今天(2020年4月24日),我为大型和小型数组选择的解决方案进行了测试。我在Chrome 81.0、Safari 13.1和Firefox 75.0上测试了它们,使用的操作系统是macOS v10.13.6(High Sierra)。

结论

对于所有浏览器:

  • 令人惊讶的是,对于小型数组,基于slicereduce(D、E、F)的非原地解决方案通常比原地解决方案快10倍至100倍。
  • 对于大型数组,基于splice的原地解决方案(AI、BI和CI)最快(有时快约100倍-但这取决于数组大小)。
  • 对于小型数组,BI解决方案最慢。
  • 对于大型数组,E解决方案最慢。

Enter image description here

细节

测试分为两组:原地解决方案(AI、BI和CI)和非原地解决方案(D、E和F),并分别针对两种情况进行了测试:

  • 一个包含10个元素的数组的测试-您可以在这里运行它。
  • 一个包含1,000,000个元素的数组的测试-您可以在这里运行它。

测试代码在下面的代码段中呈现:

jsfiddle

function AI(arr, i, el) {
  arr.splice(i, 0, el);
  return arr;
}

function BI(arr, i, el) {
  Array.prototype.splice.apply(arr, [i, 0, el]);
  return arr;
}

function CI(arr, i, el) {
  Array.prototype.splice.call(arr, i, 0, el);
  return arr;
}

function D(arr, i, el) {
  return arr.slice(0, i).concat(el, arr.slice(i));
}

function E(arr, i, el) {
  return [...arr.slice(0, i), el, ...arr.slice(i)]
}

function F(arr, i, el) {
  return arr.reduce((s, a, j)=> (j-i ? s.push(a) : s.push(el, a), s), []);
}



// -------------
// TEST
// -------------

let arr = ["a", "b", "c", "d", "e", "f"];

let log = (n, f) => {
  let a = f([...arr], 3, "NEW");
  console.log(`${n}: [${a}]`);
};

log('AI', AI);
log('BI', BI);
log('CI', CI);
log('D', D);
log('E', E);
log('F', F);
This snippet only presents tested code (it not perform tests)

以下是谷歌浏览器上一个小数组的示例结果:

在此输入图片描述


4
答案没有回答提问者的问题。 - kabirbaidhya
25
@kabirbaidhya 实际上在这个答案中,你可以找到许多解决问题的方案,但这个答案的额外价值在于它们之间的性能比较 - 我希望很多人会发现它很有用。 - Kamil Kiełczewski
3
也许你想重新构建一下你的回答,让用户首先直观地看到可能的解决方案,然后再看到它们的性能影响。目前看起来主要关注于性能。 - kabirbaidhya

40

为了实现适当的函数式编程和链式调用,Array.prototype.insert() 的发明是必不可少的。事实上,如果splice 函数返回被改变后的数组而不是完全无意义的空数组,那它本来就很完美。

Array.prototype.insert = function(i,...rest){
  this.splice(i,0,...rest)
  return this
}

var a = [3,4,8,9];
document.write("<pre>" + JSON.stringify(a.insert(2,5,6,7)) + "</pre>");

好的,那么上面提到的使用Array.prototype.splice()方法会改变原始数组,一些人可能会抱怨说“你不应该修改不属于你的东西”,这也可能是正确的。因此,为了公共福利,我想提供另一个Array.prototype.insert()方法,它不会改变原始数组。下面是代码:

Array.prototype.insert = function(i,...rest){
  return this.slice(0,i).concat(rest,this.slice(i));
}

var a = [3,4,8,9],
    b = a.insert(2,5,6,7);
console.log(JSON.stringify(a));
console.log(JSON.stringify(b));


4
“完全没有意义的空数组” - 当第二个参数为0时,它只会返回一个空数组。如果第二个参数大于0,则返回从数组中移除的项目。考虑到您正在向原型添加内容,并且 splice 改变了原始数组,我认为“适当的函数式编程”不应该与 splice 混在一起。 - chrisbajorin
我们现在讨论的是插入操作,Array.prototype.splice() 的第二个参数必须为零。它返回的值除了表示“我没有删除任何东西”之外没有其他意义,因为我们使用它来插入一个已有的项目,我们已经知道这个信息了。如果你不想改变原始数组,那么你可以通过两个 Array.prototype.slice() 和一个 Array.prototype.concat() 操作来实现相同的效果。这取决于你自己。 - Redu
1
你的第二个实现是整个页面中最干净的,但你却没有得到任何投票。请接受我的投票并继续保持良好的工作。(你应该避免改变原型,但你已经知道了这一点) - NiKo
1
我认为值得一提的是,剩余参数是ECMA 6th的新功能。(https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters) - Geza Turi

33

在这种情况下,我建议使用纯JavaScript。此外,JavaScript中没有插入方法,但我们有一种内置的Array方法可以胜任此工作。它被称为splice...

让我们看看splice()是什么...

splice()方法通过删除现有元素和/或添加新元素来更改数组的内容。

好的,假设我们有以下数组:

const arr = [1, 2, 3, 4, 5];

我们可以这样移除3

arr.splice(arr.indexOf(3), 1);
它将返回3,但如果我们现在检查arr,我们会得到:
[1, 2, 4, 5]

目前为止,一切都很好,但我们如何使用splice向数组中添加一个新元素呢?

让我们把3放回到数组中...

arr.splice(2, 0, 3);

让我们来看看我们已经做了什么......

我们再次使用 splice,但这一次作为第二个参数,我们传递了0,意味着我们不想删除任何项,但同时,我们添加了一个第三个参数,即3将被添加到第二个索引处...

您应该知道我们可以同时进行删除添加。例如,现在我们可以这样做:

arr.splice(2, 2, 3);

这将删除索引2上的两个项目。然后在索引2处添加3,结果将会是:

[1, 2, 3, 5];

这里展示了每个 splice 中的参数作用:

array.splice(start, deleteCount, item1, item2, item3 ...)


2
谢谢您的解释。我建议在数组值中使用非数字来展示这个例子,这样更好理解。例如:arr.splice (2, 0, 3) vs arr.splice(2, 0,"C")。这样读者更容易理解哪个参数映射到值部分,哪个参数映射到索引部分。谢谢! - Cristian E.

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