12種JavaScript中最常用的數組操作整理匯總
數組是最常見的數據結構之一,我們需要絕對自信地使用它。在這里,我將列出 JavaScript 中最重要的幾個數組常用操作片段,包括數組長度、替換元素、去重以及許多其他內容。
1、數組長度
大多數人都知道可以像這樣得到數組的長度:
const arr = [1, 2, 3];
console.log(arr.length); // 3
有趣的是,我們可以手動修改長度。這就是我所說的:
const arr = [1, 2, 3];
arr.length = 2;
arr.forEach(i => console.log(i)); // 1 2
甚至創建指定長度的新數組:
const arr = [];
arr.length = 100;
console.log(arr) // [undefined, undefined, undefined ...]
這不是一個很好的實踐,但是值得了解。
我們常常需要清空數組時候會使用:
const arr = [1, 2];
arr.length = 0;
console.log(arr) // []
如果 arr 的值是共享的,并且所有參與者都必須看到清除的效果,那么這就是你需要采取的方法。
但是,JavaScript 語義規定,如果減少數組的長度,則必須刪除新長度及以上的所有元素。
而且這需要花費時間(除非引擎對設置長度為零的特殊情況進行了優化)。實際上,一個性能測試表明,在所有當前的 JavaScript 引擎上,這種清除方法更快。
2、替換數組元素
有幾種方法可以解決這個問題。如果需要替換指定索引處的元素,請使用 splice:
const arr = [1, 2, 3];
arr.splice(2, 1, 4); // 將索引 2 開始的 1 元素更改為 4
console.log(arr); // [1, 2, 4]
arr.splice(0, 2, 5, 6) // 將索引 0 開始的 2 個元素更改為 5 和 6
console.log(arr); // [5, 6, 4]
splice 在數組刪除有更多的說明
如果你需要根據項目的內容替換項目,或者必須創建一個新數組,請使用 map:
const arr = [1, 2, 3, 4, 5, 6];
// 所有奇數的平方
const arr2 = arr.map(item => item % 2 == 0 ? item : item*item);
console.log(arr2); // [1, 2, 9, 4, 25, 6];
map 接受函數作為其參數。它將對數組中的每個元素調用該函數一次,并生成一個新的函數返回的項數組。
關于 map 有個經典的面試題:['1', '2', '3', '4', '5'].map(parseInt) => ?
3、過濾數組
在某些情況下,你需要刪除數組中的某些元素,然后創建一個新的元素。在這種情況下,使用在ES5中引入的很棒的 filter 方法:
const arr = [1, 2, 3, 4, 5, 6, 7];
// 過濾掉所有奇數
const arr2 = arr.filter(item => item % 2 == 0);
console.log(arr2); // [2, 4, 6];
filter 的工作原理與 map 非常相似。向它提供一個函數,filter 將在數組的每個元素上調用它。如果要在新數組中包含此特定元素,則函數必須返回 true,否則返回 false。
4、 合并數組
如果你想將多個數組合并為一個數組,有兩種方法。
Array 提供了 concat 方法:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = arr1.concat(arr2);
console.log(arr3 ); // [1, 2, 3, 4, 5, 6]
ES6 中引入了 spread operator,一種更方便的方法:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3 ); // [1, 2, 3, 4, 5, 6]
還有一種比較奇特方法:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
上面 2 種通用的方法,都不會改變原數組,最后一種奇特方法,會改變 push 的原數組,謹慎使用。
Array.prototype.push.apply 和 concat 對比:
- 數據上萬情況下,兩者性能相差毫秒個位數級別
- Array.prototype.push.apply 數組長度有限制,不同瀏覽器不同,一般不能超過十萬, concat 無限制
- Array.prototype.push.apply 會改變原數組, concat 不會
正常情況下我們都應該使用 concat 和 spread operator,有種情況下可以使用,如果頻繁合并數組可以用 Array.prototype.push.apply。
5、復制數組
總所周知,定義數組變量存儲不是數組值,而只是存儲引用。 這是我的意思:
const arr1 = [1, 2, 3];
const arr2 = arr1;
arr2[0] = 4;
arr2[1] = 2;
arr2[2] = 0;
console.log(arr1); // [4, 2, 0]
因為 arr2 持有對 arr1 的引用,所以對 arr2 的任何更改都是對 arr1 的更改。
const arr1 = [1, 2, 3];
const arr2 = arr1.slice(0);
arr2[0] = 4;
arr2[1] = 2;
arr2[2] = 0;
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [4, 2, 0]
我們也可以使用 ES6 的 spread operator:
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
arr2[0] = 4;
arr2[1] = 2;
arr2[2] = 0;
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [4, 2, 0]
也可以使用前面合并使用的 concat 方法
const arr1 = [1, 2, 3];
const arr2 = [].concat(arr1);
arr2[0] = 4;
arr2[1] = 2;
arr2[2] = 0;
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [4, 2, 0]
注意:如果想要了解更多的數組復制,請查詢 數組深拷貝和淺拷貝 相關資料,這里只實現了淺拷貝。
6、數組去重
數組去重是面試經常問的,數組去重方式很多,這里介紹比較簡單直白的三種方法:
可以使用 filter 方法幫助我們刪除重復數組元素。filter 將接受一個函數并傳遞 3 個參數:當前項、索引和當前數組。
const arr1 = [1, 1, 2, 3, 1, 5, 9, 4, 2];
const arr2 = arr1.filter((item, index, arr) => arr.indexOf(item) == index);
console.log(arr2); // [1, 2, 3, 5, 9, 4]
可以使用 reduce 方法從數組中刪除所有重復項。然而,這有點棘手。reduce 將接受一個函數并傳遞 2 個參數:數組的當前值和累加器。
累加器在項目之間保持相同,并最終返回:
const arr1 = [1, 1, 2, 3, 1, 5, 9, 4, 2];
const arr2 = arr1.reduce(
(acc, item) => acc.indexOf(item) == -1 ? [...acc, item]: acc,
[] // 初始化當前值
);
console.log(arr2); // [1, 2, 3, 5, 9, 4]
可以使用 ES6 中引入的新數據結構 set 和 spread operator:
const arr1 = [1, 1, 2, 3, 1, 5, 9, 4, 2];
const arr2 = [...(new Set(arr1))];
console.log(arr2); // [1, 2, 3, 5, 9, 4]
還有很多其他去重方式,比如使用 {} + for。
7、轉換為數組
有時我們必須將一些其它數據結構,如集合或字符串轉換為數組。
類數組:函數參數,dom 集合
Array.prototype.slice.call(arguments);
Array.prototype.concat.apply([], arguments);
字符串:
console.log('string'.split('')); // ["s", "t", "r", "i", "n", "g"]
console.log(Array.from('string')); // ["s", "t", "r", "i", "n", "g"]
集合:
console.log(Array.from(new Set(1,2,3))); // [1,2,3]
console.log([...(new Set(1,2,3))]); // [1,2,3]
8、數組遍歷
數組遍歷方式很多,有底層的,有高階函數式,我們就來介紹幾種:
for:
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// 1 2 3
for-in:
const arr = [1, 2, 3];
for (let i in arr) {
if(arr.hasOwnProperty(i)) {
console.log(arr[i]);
}
}
// 1 2 3
for-of:
const arr = [1, 2, 3];
for (let i of arr) {
console.log(i);
}
// 1 2 3
forEach:
[1, 2, 3].forEach(i => console.log(i))
// 1 2 3
while:
const arr = [1,2,3];
let i = -1;
const length = arr.length;
while(++i < length) {
console.log(arr[i])
}
// 1 2 3
迭代輔助語句:break 和 continue
- break 語句是跳出當前循環,并執行當前循環之后的語句
- continue 語句是終止當前循環,并繼續執行下一次循環
上面方式中,除了 forEach 不支持跳出循環體,其他都支持。高階函數式方式都類似 forEach 。
性能對比:
while > for > for-of > forEach > for-in
如果是編寫一些庫或者大量數據遍歷,推薦使用 while。有名的工具庫 lodash 里面遍歷全是 while。正常操作,for-of 或者 forEach 已經完全滿足需求。
下面介紹幾種高級函數式,滿足條件為 true 立即終止循環,否則繼續遍歷到整個數組完成的方法:
// ES5
[1, 2, 3].some((i) => i == 1);
// ES6
[1, 2, 3].find((i) => i == 1);
[1, 2, 3].findIndex((i) => i == 1);
其他高階函數式方法,例如 forEach map filter reduce reduceRight every sort 等,都是把整個數組遍歷。
9、扁平化多維數組
這個功能說不是很常用,但是有時候又會用到:
二維數組:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const arr2 = [].concat.apply([], arr1);
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
三維數組:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];
const arr2 = [].concat.apply([], arr1);
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, [1, 2, 3], [4, 5, 6], [7, 8, 9]]
concat.apply 方式只能扁平化二維數組,在多了就需要遞歸操作。
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];
const arr2 = flatten(arr1);
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ES6+(ES2019) 給我們提供一個 flat 方法:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const arr2 = arr1.flat();
console.log(arr2); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
默認只是扁平化二維數組,如果想要扁平化多維,它接受一個參數 depth,如果想要展開無限的深度使用 Infinity:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];
const arr2 = arr1.flat(Infinity);
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
還有一種面試扁平化二維數組方式:
const arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [[1, 2, 3], [4, 5, 6], [7, 8, 9]]];
const arr2 = arr1.toString().split(',').map(n => parseInt(n, 10));
console.log(arr2); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
10、數組添加
如何從數組中添加元素?
我們可以使用 push 從數組末尾添加元素,使用 unshift 從開頭添加元素,或者使用 splice 從中間添加元素。concat 方法可創建帶有所需項目的新數組,這是一種添加元素的更高級的方法。
從數組的末尾添加元素:
const arr = [1, 2, 3, 4, 5, 6];
arr.push(7)
console.log( arr ); // [1, 2, 3, 4, 5, 6, 7]
從數組的開頭添加元素:
const arr = [1, 2, 3, 4, 5, 6];
arr.unshift(0)
console.log( arr ); // [0, 1, 2, 3, 4, 5, 6]
push 方法的工作原理與 unshift 方法非常相似,方法都沒有參數,都是返回數組更新的 length 屬性。它修改調用它的數組。
使用 splice 添加數組元素:
只需要把 splice,第二個參數設為 0 即可,splice 在數組刪除有更多的說明
const arr = [1, 2, 3, 4, 5];
arr.splice(1, 0, 10)
console.log(arr); // [1, 10, 2, 3, 4, 5]
使用 concat 添加數組元素:
const arr1 = [1, 2, 3, 4, 5];
const arr2 = arr1.concat(6);
console.log(arr2); // [1, 2, 3, 4, 5, 6]
11、數組刪除
數組允許我們對值進行分組并對其進行遍歷。 我們可以通過不同的方式添加和刪除數組元素。 不幸的是,沒有簡單的 Array.remove 方法。
那么,如何從數組中刪除元素?
除了 delete 方式外,JavaScript 數組還提供了多種清除數組值的方法。
我們可以使用 pop 從數組末尾刪除元素,使用 shift 從開頭刪除元素,或者使用 splice 從中間刪除元素。
filter 方法可創建帶有所需項目的新數組,這是一種刪除不需要的元素的更高級的方法。
從數組的末尾刪除元素:
通過將 length 屬性設置為小于當前數組長度,可以從數組末尾刪除數組元素。 索引大于或等于新長度的任何元素都將被刪除。
const arr = [1, 2, 3, 4, 5, 6];
arr.length = 4;
console.log( arr ); // [1, 2, 3, 4]
pop 方法刪除數組的最后一個元素,返回該元素,并更新length屬性。pop 方法會修改調用它的數組,這意味著與使用 delete 不同,最后一個元素被完全刪除并且數組長度減小。
const arr = [1, 2, 3, 4, 5, 6];
arr.pop();
console.log( arr ); // [1, 2, 3, 4, 5]
從數組的開頭刪除元素:
shift 方法的工作原理與 pop 方法非常相似,只是它刪除了數組的第一個元素而不是最后一個元素。
const arr = [1, 2, 3, 4, 5, 6];
arr.shift();
console.log( arr ); // [2, 3, 4, 5, 6]
shift 和 pop 方法都沒有參數,都是返回已刪除的元素,更新剩余元素的索引,并更新 length 屬性。它修改調用它的數組。如果沒有元素,或者數組長度為 0,該方法返回 undefined。
使用 splice 刪除數組元素:
splice 方法可用于從數組中添加、替換或刪除元素。
splice 方法接收至少三個參數:
- start:在數組中開始刪除元素的位置
- deleteCount:刪除多少個元素(可選)
- items...:添加元素(可選)
splice 可以實現添加、替換或刪除。
刪除:
如果 deleteCount 大于 start 之后的元素的總數,則從 start 后面的元素都將被刪除(含第 start 位)。
如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是說,如果它大于或者等于start之后的所有元素的數量),那么start之后數組的所有元素都會被刪除。
如果 deleteCount 是 0 或者負數,則不移除元素。這種情況下,至少應添加一個新元素。
const arr1 = [1, 2, 3, 4, 5];
arr1.splice(1);
console.log(arr1); // [1];
const arr2 = [1, 2, 3, 4, 5];
arr2.splice(1, 2)
console.log(arr2); // [1, 4, 5]
const arr3 = [1, 2, 3, 4, 5];
arr3.splice(1, 1)
console.log(arr3); // [1,3, 4, 5]
添加:
添加只需要把 deleteCount 設置為 0,items 就是要添加的元素。
const arr = [1, 2, 3, 4, 5];
arr.splice(1, 0, 10)
console.log(arr); // [1, 10, 2, 3, 4, 5]
替換:
添加只需要把 deleteCount 設置為和 items 個數一樣即可,items 就是要添加的元素。
const arr = [1, 2, 3, 4, 5];
arr.splice(1, 1, 10)
console.log(arr); // [1, 10, 3, 4, 5]
注意:splice 方法實際上返回兩個數組,即原始數組(現在缺少已刪除的元素)和僅包含已刪除的元素的數組。如果循環刪除元素或者多個相同元素,最好使用倒序遍歷。
使用 delete 刪除單個數組元素:
使用 delete 運算符不會影響 length 屬性。它也不會影響后續數組元素的索引。數組變得稀疏,這是說刪除的項目沒有被刪除而是變成 undefined 的一種奇特的方式。
const arr = [1, 2, 3, 4, 5];
delete arr[1]
console.log(arr); // [1, empty, 3, 4, 5]
實際上沒有將元素從數組中刪除的原因是 delete 運算符更多的是釋放內存,而不是刪除元素。 當不再有對該值的引用時,將釋放內存。
使用數組 filter 方法刪除匹配的元素:
與 splice 方法不同,filter 創建一個新數組。
filter 接收一個回調方法,回調返回 true 或 false。返回 true 的元素被添加到新的經過篩選的數組中。
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
const filtered = arr.filter((value, index, arr) => value > 5);
console.log(filtered); // [6, 7, 8, 9]
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
清除或重置數組:
最簡單和最快的技術是將數組變量設置為空數組
let arr = [1,2,3];
arr = [];
清除數組的一個簡單技巧是將其 length 屬性設置為 0。
let arr = [1,2,3];
arr.length = 0;
使用 splice 方法,不傳遞第二個參數。這將返回原始元素的一個副本,這對于我們的有些場景可能很方便。也是一種數組復制方法技巧。
let arr = [1,2,3];
arr.splice(0);
使用 while 循環,這不是一種常用清除數組的方法,但它確實有效,而且可讀性強。一些性能測試也顯示這是最快的技術。
const arr = [1, 2, 3, 4, 5, 6];
while (arr.length) { arr.pop(); }
console.log(arr); // []
12、其他方法
剔除假值:
[1, false, '', NaN, 0, [], {}, '123'].filter(Boolean) // [1, [], {}, '123']
是否有一個真值:
[1, false, '', NaN, 0, [], {}, '123'].some(Boolean) // true
是否全部都是真值:
[1, false, '', NaN, 0, [], {}, '123'].every(Boolean) // false
補零:
Array(6).join('0'); // '00000' 注意:如果要補5個0,要寫6,而不是5。
Array(5).fill('0').join('') // '00000'
數組最大值和最小值:
Math.max.apply(null, [1, 2, 3, 4, 5]) // 5
Math.min.apply(null, [1, 2, 3, 4, 5]) // 1
判斷回文字符串:
const str1 = 'string';
const str2 = str1.split('').reverse().join('');
console.log(str1 === str2); // false
數組模擬隊列:
隊列先進先出:
const arr = [1];
// 入隊
arr.push(2);
console.log('入隊元素:', arr[arr.length -1]); // 2
// 出隊
console.log('出隊元素:', arr.shift()); // 1
獲取數組最后一個元素:
像我們平常都是這樣來獲?。?/span>
const arr = [1, 2, 3, 4, 5];
console.log(arr[arr.length - 1]); // 5
感覺很麻煩,不過 ES 有了提案,未來可以通過 arr[-1] 這種方式來獲取,Python 也有這種風騷的操作:
目前我們可以借助 ES6 的 Proxy 對象來實現:
const arr1 = [1, 2, 3, 4, 5];
function createNegativeArrayProxy(array) {
if (!Array.isArray(array)) {
throw new TypeError('Expected an array');
}
return new Proxy(array, {
get: (target, prop, receiver) => {
prop = +prop;
return Reflect.get(target, prop < 0 ? target.length + prop : prop, receiver);;
}
})
}
const arr2 = createNegativeArrayProxy(arr1);
console.log(arr1[-1]) // undefined
console.log(arr1[-2]) // undefined
console.log(arr2[-1]) // 5
console.log(arr2[-2]) // 4
注意:這樣方式雖然有趣,但是會引起性能問題,50萬次循環下,在Chrome瀏覽器,代理數組的執行時間大約為正常數組的50倍,在Firefox瀏覽器大約為20倍。在大量循環情況下,請慎用。無論是面試還是學習,你都應該掌握 Proxy 用法。