天天看點

拘神遣将--最詳全的JavaScript數組方法總結

前言

本文試圖地将JS中包含的全部的數組方法盡數列舉,介紹它們每一個的定義、用法,并附帶一些執行個體甚至應用場景。是以篇幅不會很短,但絕不冗長。

内容覆寫ES5和ES6中的31種數組方法 ,介紹順序先ES5後ES6,對于相似方法會進行類比,但不會連帶。

為求準确,所有方法的描述文案均引自一些JS領域的權威,本文中引用的文案如下:

ES5部分來自  《MDN(Mozilla Developer Network 》

ES6部分來自 《ECMAScript 6 入門》(作者是阮一峰)

開始之前,劣者先将本文談到的方法按講解次序陣列下方,供讀者參考:

ES5:
join、push、pop、shift、unshift、sort、reverse、concat、
slice、splice、indexOf、lastIndexOf、forEach、map、filter、
every、some、reduce、reduceRight

ES6:
form、of、copyWithin、find、findIndex、fill、entries、
keys、values、includes、flat、flatMap
           

正文

正文内容所含的數組方法,排名不分先後,均按照劣者使用多寡所排列。

對于某幾個應用場景比較廣泛的方法,劣者會将其用*标注,對于難以掌握的劣者會詳細書寫。

1、join(): 将一個數組(或一個類數組對象)的所有元素連接配接成一個字元串并傳回這個字元串。如果數組隻有一個項目,那麼将傳回該項目而不使用分隔符。

非常簡短,不多贅述,看一眼下面的代碼,便能學會使用它。該方法不會改變原數組。

let arr = ['維', '天', '有', '漢']

arr.join('-')
// 維-天-有-漢

arr.join()
// 維,天,有,漢
           
2、*push(): 方法将一個或多個元素添加到數組的末尾,并傳回該數組的新長度。

既常見又簡單。

let arr = ['秦', '漢', '唐', '宋']

console.log(arr.push('明'))
// 5

console.log(arr)
// ['秦', '漢', '唐', '宋', '明']
           
3、pop(): 方法從數組中删除最後一個元素,并傳回該元素的值。此方法更改數組的長度。
let arr = ["Tony", "Banner", "Thor"];

let item = arr.pop();

console.log(item); // Thor

console.log(arr); // ["Tony", "Banner"]
           
4、shift(): 方法從數組中删除第一個元素,并傳回該元素的值。此方法更改數組的長度。
let arr = ["Tony", "Banner", "Thor"];

let item = arr.shift();

console.log(item); // Tony

console.log(arr); // [ "Banner", "Thor"]
           
5、unshift(): 方法将一個或多個元素添加到數組的開頭,并傳回該數組的新長度(該方法修改原有數組)。
var arr = ["Tony", "Banner", "Thor"];

var count = arr.unshift("Steve");

console.log(count); // 4

console.log(arr); //["Steve", "Tony", "Banner", "Thor"]
           
6、sort():方法用原地算法對數組的元素進行排序,并傳回數組。排序算法現在是穩定的。預設排序順序是根據字元串Unicode碼點。

該方法會改變原數組。

下面的代碼,是對String和Number兩種資料類型的舉例:

var arr1 = ["a", "d", "c", "b"];

console.log(arr1.sort()); // ["a", "b", "c", "d"]

arr2 = [13, 24, 51, 3];

console.log(arr2.sort()); // [13, 24, 3, 51]

console.log(arr2); // [13, 24, 3, 51](原數組被改變)
           

可以看到,上面代碼中的arr2數組在調用sort()後未能得到期望的結果,這是因為在排序時,sort()方法會調用每個數組項的 toString()轉型方法,然後比較得到的字元串,以确定如何排序。即使數組中的每一項都是數值, sort()方法比較的也是字元串。

當然,這個問題是完全可以規避的。

sort()方法可以接收一個比較函數作為參數,以便我們指定哪個值位于哪個值的前面。比較函數接收兩個參數,如果第一個參數應該位于第二個之前則傳回一個負數,如果兩個參數相等則傳回 0,如果第一個參數應該位于第二個之後則傳回一個正數。以下就是一個簡單的比較函數:

function compare(value1, value2) {

    if (value1 < value2) {

        return -1;

    } else if (value1 > value2) {

        return 1;

    } else {

        return 0;

    }

}

arr2 = [13, 24, 51, 3];

console.log(arr2.sort(compare)); // [3, 13, 24, 51]
           
7、reverse():方法将數組中元素的位置颠倒,并傳回該數組。該方法會改變原數組。
let arr = [13, 24, 51, 3];

console.log(arr.reverse()); //[3, 51, 24, 13]

console.log(arr); //[3, 51, 24, 13](原數組改變)
           
8、concat():方法用于合并兩個或多個數組。此方法不會更改現有數組,而是傳回一個新數組。
let arr = [1,3,5,7];

let arrCopy = arr.concat(9,[11,13]);

console.log(arrCopy); //[1, 3, 5, 7, 9, 11, 13]

console.log(arr); // [1, 3, 5, 7](原數組未被修改)
           

從上面測試結果可以發現:傳入的不是數組,則直接把參數添加到數組後面,如果傳入的是數組,則将數組中的各個項添加到數組中。但是如果傳入的是一個二維數組呢?   

let arrCopy2 = arr.concat([9,[11,13]]);

console.log(arrCopy2); //[1, 3, 5, 7, 9, Array[2]]

console.log(arrCopy2[5]); //[11, 13]
           

上面的代碼中,arrCopy2數組的第五項是一個包含兩項的數組,也就是說concat方法隻能将傳入數組中的每一項添加到數組中,如果傳入數組中有些項是數組,那麼也會把這一數組項當作一項添加到arrCopy2中。

9、slice():方法傳回一個新的數組對象,這一對象是一個由 begin 和 end 決定的原數組的淺拷貝(包括 begin,不包括end)。原始數組不會被改變。

該方法的兩個參數遵循[begin, end)模式,下面是一些例子:

let arr = [1,3,5,7,9,11];


let arrCopy = arr.slice(1);
//[3, 5, 7, 9, 11]

let arrCopy2 = arr.slice(1,4);
//[3, 5, 7]

let arrCopy3 = arr.slice(1,-2);
//[3, 5, 7]

let arrCopy4 = arr.slice(-4,-1);
//[5, 7, 9]

console.log(arr); 
//[1, 3, 5, 7, 9, 11](原數組沒變)
           

arrCopy隻設定了一個參數,也就是起始下标為1,是以傳回的數組為下标1(包括下标1)開始到數組最後。

arrCopy2設定了兩個參數,傳回起始下标(包括1)開始到終止下标(不包括4)的子數組。

arrCopy3設定了兩個參數,終止下标為負數,當出現負數時,将負數加上數組長度的值(6)來替換該位置的數,是以就是從1開始到4(不包括)的子數組。

arrCopy4中兩個參數都是負數,是以都加上數組長度6轉換成正數,是以相當于slice(2,5)。

10、splice():方法通過删除或替換現有元素或者原地添加新的元素來修改數組,并以數組形式傳回被修改的内容。此方法會改變原數組。

splice():很強大的數組方法,它有很多種用法,可以實作删除、插入和替換。

删除:可以删除任意數量的項,隻需指定 2 個參數:要删除的第一項的位置和要删除的項數。例如, splice(0,2)會删除數組中的前兩項。

插入:可以向指定位置插入任意數量的項,隻需提供 3 個參數:起始位置、 0(要删除的項數)和要插入的項。例如,splice(2,0,4,6)會從目前數組的位置 2 開始插入4和6。

替換:可以向指定位置插入任意數量的項,且同時删除任意數量的項,隻需指定 3 個參數:起始位置、要删除的項數和要插入的任意數量的項。插入的項數不必與删除的項數相等。例如,splice (2,1,4,6)會删除目前數組位置 2 的項,然後再從位置 2 開始插入4和6。

splice()方法始終都會傳回一個數組,該數組中包含從原始數組中删除的項,如果沒有删除任何項,則傳回一個空數組。

var arr = [1,3,5,7,9,11];

var arrRemoved = arr.splice(0,2);

console.log(arr); //[5, 7, 9, 11]

console.log(arrRemoved); //[1, 3]

var arrRemoved2 = arr.splice(2,0,4,6);

console.log(arr); // [5, 7, 4, 6, 9, 11]

console.log(arrRemoved2); // []

var arrRemoved3 = arr.splice(1,1,2,4);

console.log(arr); // [5, 2, 4, 4, 6, 9, 11]

console.log(arrRemoved3); //[7]
           
11、*indexOf():方法傳回在數組中可以找到一個給定元素的第一個索引,如果不存在,則傳回-1
let arr = [1,3,5,7,7,5,3,1];

console.log(arr.indexOf(5)); //2

console.log(arr.indexOf(5,2)); //2

console.log(arr.indexOf("5")); //-1
           
12、lastIndexOf():方法傳回指定值在調用該方法的字元串中最後出現的位置,如果沒找到則傳回 -1。從該字元串的後面向前查找,從 fromIndex 處開始
var arr = [1,3,5,7,7,5,3,1];

console.log(arr.lastIndexOf(5)); //5

console.log(arr.lastIndexOf(5,4)); //2
           
13、*forEach():方法對數組的每個元素執行一次提供的函數
let arr = [1, 2, 3, 4, 5];

arr.forEach((x, index, a) => {

console.log(x + '|' + index + '|' + (a === arr));

});

// 輸出為:

// 1|0|true

// 2|1|true

// 3|2|true

// 4|3|true

// 5|4|true
           
14、*map():方法建立一個新數組,其結果是該數組中的每個元素都調用一個提供的函數後傳回的結果。

簡單又實用的數組方法。

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

let arr2 = arr.map((item) => {

return item*item;

});

console.log(arr2); //[1, 4, 9, 16, 25]
           
15、filter():方法建立一個新數組, 其包含通過所提供函數實作的測試的所有元素。

該方法的作用就像它的名字一樣,會将數組中不合條件的項過濾掉。

下面的代碼,用filter()完成了數組去重:

let arr = [1, 2, 3, 3, 5, 6, 8, 8, 9, 10];

let arr2 = arr.filter((x, index) => {

  return arr[index] !== arr[index + 1]
  
});

console.log(arr2); 
//[1, 3, 5, 6, 8, 9, 10]
           

除了filter(),數組去重還有很多種方法,此處暫且不表。

16、every():方法測試一個數組内的所有元素是否都能通過某個指定函數的測試。它傳回一個布爾值。

傳回的是一個布爾值。

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

let arr2 = arr.every(function(x) {

return x < 10;

});

console.log(arr2); //true

let arr3 = arr.every(function(x) {

return x < 3;

});

console.log(arr3); // false
           
17、some():方法測試是否至少有一個元素可以通過被提供的函數方法。該方法傳回一個Boolean類型的值。

傳回的是一個布爾值,注意與every()類比。

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

let arr2 = arr.some(function(x) {

return x < 3;

});

console.log(arr2); //true

let arr3 = arr.some(function(x) {

return x < 1;

});

console.log(arr3); // false
           
18、reduce():方法對數組中的每個元素執行一個由您提供的reducer函數(升序執行),将其結果彙總為單個傳回值。

傳給 reduce()和 reduceRight()的函數接收 4 個參數:

前一個值、目前值、項的索引和數組對象。

這個函數傳回的任何值都會作為第一個參數自動傳給下一項。第一次疊代發生在數組的第二項上,是以第一個參數是數組的第一項,第二個參數就是數組的第二項。

let values = [1,2,3,4,5];

let sum = values.reduceRight((prev, cur, index, array) => {

return prev + cur;

},10);

console.log(sum); //25
           
19、reduceRight():方法接受一個函數作為累加器(accumulator)和數組的每個值(從右到左)将其減少為單個值。
let values = [1,2,3,4,5];

let sum = values.reduceRight((prev, cur, index, array) => {

return prev + cur;

},10);

console.log(sum); //25
           
20、*Array.from():方法用于将兩類對象轉為真正的數組:類似數組的對象(array-like object)和可周遊(iterable)的對象(包括 ES6 新增的資料結構 Set 和 Map)

首先看第一類:類似數組的對象,代碼如下:

let arrayLike = {
    '0': 'tony',
    '1': 'banner',
    '2': 'thor',
    length: 3
};

// 在ES6中使用from()方法

let arr = Array.from(arrayLike); // ['tony', 'banner', 'thor']
           

所謂類似數組的對象,本質特征隻有一點,即必須有

length

屬性。是以,任何有

length

屬性的對象,都可以通過

Array.from

方法轉為數組。

然後是第二類:可周遊(iterable)的對象,下面代碼舉例:

Array.from('Ming')
// ['M', 'i', 'n', 'g']

let mySet = new Set(['han', 'tang'])
Array.from(mySet) 
// ['han', 'tang']
           

所有裝配了Iterable接口的資料結構,from()都會忠實地将其轉換為數組。如果參數是一個真正的數組,

Array.from

會傳回一個一模一樣的新數組。

該方法也擁有第二個參數,作用類似與map(),可以将轉成的數組中的每一項,都做一個處理,下面看一個執行個體:

Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
           

Array.from()

可以将各種值轉為真正的數組,并且還提供

map

功能。這實際上意味着,隻要有一個原始的資料結構,你就可以先對它的值進行處理,然後轉成規範的數組結構,進而就可以使用數量衆多的數組方法。

let dynastyObj = {
    '0' : 'HAN',
    '1' : 'TANG',
    '2' : 'SONG',
    '3' : 'MING',
    length: 4
  }
  let dynastyArr = Array.from(dynastyObj, x => x += 'dynasty')

  // ["HANdynasty", "TANGdynasty", "SONGdynasty", "MINGdynasty"]
           
21、*Array.of():方法用于将一組值,轉換為數組。

Array.of

基本上可以用來替代

Array()

new Array()

,并且不存在由于參數不同而導緻的重載。它的行為非常統一。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
           

Array.of

總是傳回參數值組成的數組。如果沒有參數,就傳回一個空數組。

Array.of

方法可以用下面的代碼模拟實作。

function ArrayOf(){
  return [].slice.call(arguments);
           
22、copyWithin():方法,在目前數組内部,将指定位置的成員複制到其他位置(會覆寫原有成員),然後傳回目前數組。也就是說,使用這個方法,會修改目前數組。

數組執行個體的

copyWithin()

方法,在目前數組内部,将指定位置的成員複制到其他位置(會覆寫原有成員),然後傳回目前數組。也就是說,使用這個方法,會修改目前數組。

Array.prototype.copyWithin(target, start = 0, end = this.length)
           

它接受三個參數。

  • target(必需):從該位置開始替換資料。如果為負值,表示倒數。
  • start(可選):從該位置開始讀取資料,預設為 0。如果為負值,表示從末尾開始計算。
  • end(可選):到該位置前停止讀取資料,預設等于數組長度。如果為負值,表示從末尾開始計算。

這三個參數都應該是數值,如果不是,會自動轉為數值。

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
           

上面代碼表示将從 3 号位直到數組結束的成員(4 和 5),複制到從 0 号位開始的位置,結果覆寫了原來的 1 和 2。

下面是更多例子。

// 将3号位複制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相當于3号位,-1相當于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 将3号位複制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 将2号位到數組結束,複制到0号位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 對于沒有部署 TypedArray 的 copyWithin 方法的平台
// 需要采用下面的寫法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
           
23、find():方法,用于找出第一個符合條件的數組成員。它的參數是一個回調函數,所有數組成員依次執行該回調函數,直到找出第一個傳回值為true的成員,然後傳回該成員。如果沒有符合條件的成員,則傳回undefined。

find()的參數是一個回調函數,它隻會傳回一個值。

[1, 4, -5, 10].find((n) => n < 0)
// -5
           

上面代碼找出數組中第一個小于 0 的成員。

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
           

上面代碼中,

find

方法的回調函數可以接受三個參數,依次為目前的值、目前的位置和原數組。

24、findIndex():數組執行個體的findIndex方法的用法與find方法非常類似,傳回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則傳回-1。

數組執行個體的

findIndex

方法的用法與

find

方法非常類似,傳回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則傳回

-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
           

這兩個方法都可以接受第二個參數,用來綁定回調函數的

this

對象。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26
           

上面的代碼中,

find

函數接收了第二個參數

person

對象,回調函數中的

this

對象指向

person

對象。

另外,這兩個方法都可以發現

NaN

,彌補了數組的

indexOf

方法的不足。

[NaN].indexOf(NaN)
// -1

[NaN].findIndex(y => Object.is(NaN, y))
// 0
           

上面代碼中,

indexOf

方法無法識别數組的

NaN

成員,但是

findIndex

方法可以借助

Object.is

方法做到。

25、fill():方法使用給定值,填充一個數組。

fill

方法使用給定值,填充一個數組。

['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]
           

上面代碼表明,

fill

方法用于空數組的初始化非常友善。數組中已有的元素,會被全部抹去。

fill

方法還可以接受第二個和第三個參數,用于指定填充的起始位置和結束位置。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
           

上面代碼表示,

fill

方法從 1 号位開始,向原數組填充 7,到 2 号位之前結束。

26、entries():用于周遊數組。它們都傳回一個周遊器對象,可以用for...of循環進行周遊,entries()是對鍵值對的周遊。
for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1
           
27、keys():用于周遊數組。它們都傳回一個周遊器對象,可以用for...of循環進行周遊,keys()是對鍵名的周遊。
for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'
           
28、values():用于周遊數組。它們都傳回一個周遊器對象,可以用for...of循環進行周遊,values()是對鍵值的周遊
for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
           
29、includes():方法傳回一個布爾值,表示某個數組是否包含給定的值,與字元串的includes方法類似。

Array.prototype.includes

方法傳回一個布爾值,表示某個數組是否包含給定的值,與字元串的

includes

方法類似。ES2016 引入了該方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
           

該方法的第二個參數表示搜尋的起始位置,預設為

。如果第二個參數為負數,則表示倒數的位置,如果這時它大于數組長度(比如第二個參數為

-4

,但數組長度為

3

),則會重置為從

開始。

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
           

沒有該方法之前,我們通常使用數組的

indexOf

方法,檢查是否包含某個值。

if (arr.indexOf(el) !== -1) {
  // ...
}
           

indexOf

方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,是以要去比較是否不等于

-1

,表達起來不夠直覺。二是,它内部使用嚴格相等運算符(

===

)進行判斷,這會導緻對

NaN

的誤判。

[NaN].indexOf(NaN)
// -1
           

includes

使用的是不一樣的判斷算法,就沒有這個問題。

[NaN].includes(NaN)
// true
           
30、flat():數組的成員有時還是數組,Array.prototype.flat()用于将嵌套的數組“拉平”,變成一維的數組。

該方法傳回一個新數組,對原資料沒有影響。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
           

上面代碼中,原數組的成員裡面有一個數組,

flat()

方法将子數組的成員取出來,添加在原來的位置。

flat()

預設隻會“拉平”一層,如果想要“拉平”多層的嵌套數組,可以将

flat()

方法的參數寫成一個整數,表示想要拉平的層數,預設為1。

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
           

上面代碼中,

flat()

的參數為2,表示要“拉平”兩層的嵌套數組。

如果不管有多少層嵌套,都要轉成一維數組,可以用

Infinity

關鍵字作為參數。

[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
           

如果原數組有空位,

flat()

方法會跳過空位。

[1, 2, , 4, 5].flat()
// [1, 2, 4, 5]
           
31、flatMap():方法對原數組的每個成員執行一個函數(相當于執行Array.prototype.map()),然後對傳回值組成的數組執行flat()方法。該方法傳回一個新數組,不改變原數組。

flatMap()

隻能展開一層數組。

// 相當于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
           

上面代碼中,周遊函數傳回的是一個雙層的數組,但是預設隻能展開一層,是以

flatMap()

傳回的還是一個嵌套數組。

flatMap()

方法的參數是一個周遊函數,該函數可以接受三個參數,分别是目前數組成員、目前數組成員的位置(從零開始)、原數組。

arr.flatMap(function callback(currentValue[, index[, array]]) {
  // ...
}[, thisArg])
           

flatMap()

方法還可以有第二個參數,用來綁定周遊函數裡面的

this

後記

劣者有意将本文在後續不斷完善,保證本文在未來随着自己經驗的積累,能為讀者們提供一些更獨到也更實用的寫法和使用技巧。

繼續閱讀