JavaScript數據結構之數組

幾乎所有的編程語言都原生支持數組類型,因為其是最簡單的內存數據結構。數組也是 JavaScript 中最常見的數據結構之一,它提供了很多處理存儲數據的方法。JavaScript 中,數組是經過改進的對象,和其他語言不同的是,數組中每個槽位可以存儲任意類型的數據,這意味著可以創建一個數組,它的第一個元素是字符串、第二個元素是數字、第三個是對象。在 JavaScript 中擁有許多很實用的方法,本文就來總結一下數組中常用的操作方法。

1. Array.map()

使用.map() 方法,可以創建一個基于原始數組的修訂版數組。.map() 方法接受一個函數,該函數遍歷數組中的所有項并進行相應的修改。

語法

const newArray = array.map(function callback(currentValue[, index[, array]]) {
 // 為新數組返回新的元素
}[, thisArg])

map 函數用于遍歷數組元素。它接受一個回調函數作為參數,根據回調函數返回一個新數組和新元素。

參數

  • callback(必須):生成新數組元素的函數,接收三個參數:

    • currentValuecallback 數組中正在處理的當前元素。
    • index:可選,callback 數組中正在處理的當前元素的索引。
    • array:可選,map 方法調用的數組。
  • thisArg:可選,執行 callback 函數時值被用作 this

返回值

一個由原數組每個元素執行回調函數的結果組成的新數組。

當需要更新數組中的所有項并將其存儲到一個新數組中時,.map() 方法就可以派上用場了。

實例

例如有一個文章列表的數組,如下:

const articles = [
    {
        article_id: "6976209276364652558",
        title: "如何在 Vue 的計算屬性中傳遞參數",
        views: 1258,
    },
    {
        article_id: "6976113133358153736",
        title: "Angular數據狀態管理框架:NgRx/Store",
        views: 2258,
    },
    {
        article_id: "6975722363241365534",
        title: "Angular管道PIPE介紹",
        views: 1568,
    },
];

現在基于上面文章列表數組,獲取所有 title 組成的數組,如下:

const arrayTitles = articles.map((item) => item.title);
console.log(arrayTitles);

輸出的結果如下:

[
  '如何在 Vue 的計算屬性中傳遞參數',
  'Angular數據狀態管理框架:NgRx/Store',
  'Angular管道PIPE介紹'
]

當然,只要是數組都可以使用 .map() ,接下來就基于上面標題數組,增加作者信息,如下:

const arrayTitlesWithAuthor = arrayTitles.map(
    (title) => `《${title}》作者:天行無忌`
);
console.log(arrayTitlesWithAuthor);

輸出結果如下:

[
  '《如何在 Vue 的計算屬性中傳遞參數》作者:天行無忌',
  '《Angular數據狀態管理框架:NgRx/Store》作者:天行無忌',
  '《Angular管道PIPE介紹》作者:天行無忌'
]

.map() 方法是一個用來創建新數組、修改其內容并保持原始數組不變的通用方法。當出現需要修改現有數組的內容并將結果存儲為新變量的時候就可以用。

2. Array.filter()

從方法名稱可以很容易知道其用途,沒錯用于過濾數組元素。

filter()方法根據特定條件獲取數組中的元素,像 .map() 方法一樣,它將返回一個新數組,并保持原始數組不變。

語法

const newArray = array.filter(callback(element[, index[, array]])[, thisArg])
  • callback :用來測試數組的每個元素的回調函數,返回 true 表示該元素通過測試,保留該元素,false 則不保留。它接受以下三個參數:
    • element:數組中當前正在處理的元素。
    • index:可選,正在處理的元素在數組中的索引。
    • array:可選,調用了 filter 的數組本身。
  • thisArg:可選,執行 callback 時,用于 this 的值

返回值

一個新的、由通過測試的元素組成的數組,如果沒有任何數組元素通過測試,則返回空數組。

實例

基于上面的 articles 數組,分別獲取 views 小于 2000 的和大于 2000 的文章列表:

const lessArticles = articles.filter((item) => item.views < 2000);
const muchArticles = articles.filter((item) => item.views > 2000);
console.log(lessArticles);
console.log("\r\n==========================================\r\n");
console.log(muchArticles);

輸出的結果如下:

[
  {
    article_id: '6976209276364652558',
    title: '如何在 Vue 的計算屬性中傳遞參數',
    views: 1258
  },
  {
    article_id: '6975722363241365534',
    title: 'Angular管道PIPE介紹',
    views: 1568
  }
]

==========================================

[
  {
    article_id: '6976113133358153736',
    title: 'Angular數據狀態管理框架:NgRx/Store',
    views: 2258
  }
]

當需要從數組中刪除不符合特定條件的元素時,可以使用 .filter()

3. Array.find()

.find() 方法看起來很像前面介紹的.filter()方法。跟 .filter()方法一樣,將匹配的條件的元素返回,區別在于,.find() 將只返回與提供的條件匹配的第一個元素,不是數組。

語法

arr.find(callback[, thisArg])
  • callback:在數組每一項上執行的函數,接收 3 個參數:
    • element:當前遍歷到的元素。
    • index:可選,當前遍歷到的索引。
    • array:可選數組本身。
  • thisArg:可選執行回調時用作 this 的對象。

返回值

數組中第一個滿足所提供測試函數的元素的值,否則返回 undefined

實例

將上面的 .filter() 方法改為 .find(),如下:

const lessArticle = articles.find((item) => item.views < 2000);
const muchArticle = articles.find((item) => item.views > 2000);
console.log(lessArticle);
console.log("\r\n==========================================\r\n");
console.log(muchArticle);

輸出結果如下:

{
  article_id: '6976209276364652558',
  title: '如何在 Vue 的計算屬性中傳遞參數',
  views: 1258
}

==========================================

{
  article_id: '6976113133358153736',
  title: 'Angular數據狀態管理框架:NgRx/Store',
  views: 2258
}

什么時候使用 Array.find() ? 當需要獲取數組通過定義條件的第一個元素時。

4. Array.findIndex()

.findIndex() 方法在名稱上跟 .find() 前半部分一樣,其實現的功能和.find() 一樣,其區別在于返回值不一樣,只返回與提供的條件匹配的第一個元素的索引值。

const lessArticle = articles.findIndex((item) => item.views < 2000);
const muchArticle = articles.findIndex((item) => item.views > 2000);
console.log(lessArticle); // 0
console.log(muchArticle); // 1

什么時候使用 Array.findIndex() ? 當需要獲取數組通過定義條件的第一個元素所在數組中的索引值時。

5. Array.forEach()

.forEach() 方法的工作方式很像常規的 for 循環,遍歷一個數組并在每個元素上執行一個函數。.forEach() 的第一個參數是回調函數,它包含循環數組的當前值和索引。

如下:

articles.forEach((item, index) => {
    console.log(`文章索引 ${index} 的標題為《${item.title}》`);
});

輸出結果如下:

文章索引 0 的標題為《如何在 Vue 的計算屬性中傳遞參數》
文章索引 1 的標題為《Angular數據狀態管理框架:NgRx/Store》
文章索引 2 的標題為《Angular管道PIPE介紹》

當需要簡單地循環遍歷數組的每個元素而不需要構建新數組時。

6. for...of

for...of 是es6推出的迭代器,號稱最簡潔,可以是用 breakcontinuereturn 終止循環。跟 .forEach() 不同的是,不提供數組索引。跟 for 語句相比代碼少得多,更簡潔。

下面代碼遍歷輸出數組,如下:

for (const item of articles) {
    console.log(item);
}

7. for...in

這個方法跟上面的for...of 語法上看起來相似,for...of 是對值的遍歷,for...in 是對 key/index 的遍歷。for...in 應用于數組則 key 對應的就是數組的索引值,應用于對象則 key 對應鍵值。

來看代碼執行效果,先應用于數組,如下:

for (const key in articles) {
    console.log(key);
}

上面代碼輸出的是數組的索引值:0、1、2,下面應用于數組第一個對象,如下:

for (const key in articles[0]) {
    console.log(key);
}

輸出的就是:article_idtitleviews

在實際開發中不提倡使用 for...in,如果需要遍歷對象屬性,推薦使用Object.keys

8. Array.every()

.every()方法將檢查數組中的每個元素是否都通過提供的條件,如果數組中的所有元素都通過條件,則將返回 true ,如果沒有,將返回 false

語法

arr.every(callback(element[, index[, array]])[, thisArg])
  • callback:用來測試每個元素的函數,它可以接收三個參數:

    • element:用于測試的當前值。
    • index:可選,用于測試的當前值的索引。
    • array:可選,調用 every 的當前數組。
  • thisArg:執行 callback 時使用的 this 值。

返回值

如果回調函數的每一次返回都為 true 值,返回 true ,否則返回 false,即跟邏輯判斷的 && 類似,所有結果為真才為真。

實例

例如,檢查 articles 數組所有的文章 views 都超過 1000,如下:

const isMoreThan1000 = articles.every((item) => item.views > 1000);
console.log(isMoreThan1000);  // true

檢查 articles 數組所有的文章 views 都超過 2000,如下:

const isMoreThan2000 = articles.every((item) => item.views > 2000);
console.log(isMoreThan2000); // false

什么時候使用 Array.every() ? 當需要確認數組的每一項都通過定義的條件時。

9. Array.some()

.some() 方法和 .every() 方法類似,但驗證的結果是相反的,如果數組中的所有元素只要有一個通過條件,則將返回 true ,如果所有的元素都不通過條件,將返回 false

.some() 方法和 .every() 方法在一些邏輯處理中,可以實現現邏輯 andor

語法

arr.some(callback(element[, index[, array]])[, thisArg])
  • callback:用來測試每個元素的函數,它可以接收三個參數:

    • element:用于測試的當前值。
    • index:可選,用于測試的當前值的索引。
    • array:可選,調用 some 的當前數組。
  • thisArg:執行 callback 時使用的 this 值。

返回值

數組中有至少一個元素通過回調函數的測試就會返回 true,所有元素都沒有通過回調函數的測試返回值才會為 false。即跟邏輯判斷的 || 類似,所有結果為假才為假。

實例

例如,檢查 articles 數組所有的文章 views 是否有 views 超過 2000 的文章,如下:

const isMore2000 = articles.some((item) => item.views > 2000);
console.log(isMore2000); // true

檢查 articles 數組所有的文章 是否有 views 超過 3000 的文章,如下:

const isMore3000 = articles.some((item) => item.views > 3000);
console.log(isMore3000);  // false

10. Array.reduce()

reduce() 方法對數組中的每個元素執行 reducer 函數(升序執行),將其結果匯總為單個返回值。

語法

Array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

實現了為數組中的每一個元素依次執行 callback 函數,不包括數組中被刪除或從未被賦值的元素。

參數

  • callback(必須):執行數組中每個值 (如果沒有提供 initialValue 則第一個值除外)的reducer函數,包含四個參數

  • initialValue(可選):作為第一次調用 callback 函數時的第一個參數的值。** 如果沒有提供初始值,則將使用數組中的第一個元素**。 在沒有初始值的空數組上調用 reduce 將報錯。

返回值

函數累計處理的結果。

.reduce() 方法接受一個回調函數作為其第一個參數,一個可選的初始值作為其第二個參數。如果沒有提供初始值,則使用第一個數組元素作為值。回調函數提供一個累加器 accumulatorcurrentValue 參數,用于執行 reduce 計算。

實例

const arrNumbers = [1, 2, 3, 4, 5];
const reduceNumbers = (arrayNumbers, accumulatorInitVal = false) => {
    const reduceCallback = (accumulator, currentVal, currentIndex) => {
        console.log(`當前索引:${currentIndex}`);
        return accumulator + currentVal;
    };
    return accumulatorInitVal
        ? arrayNumbers.reduce(reduceCallback, accumulatorInitVal)
        : arrayNumbers.reduce(reduceCallback);
};

console.log(reduceNumbers(arrNumbers)); // 15,累計器初始值為數組的第一個元素的值1
console.log(reduceNumbers(arrNumbers, 10)); // 25,累計器初始值為10

console.log (當前索引:${currentIndex}),是為了更加直觀的看到索引值。

第一次未定義初始值輸出如下:

當前索引:1
當前索引:2
當前索引:3
當前索引:4

第二次定義了累計器初始值輸出如下:

當前索引:0
當前索引:1
當前索引:2
當前索引:3
當前索引:4

先來一個簡單的實例,對 articles 數組的 views 進行累加求和:

const sumViews = articles.reduce(
    (accumulator, current) => accumulator + current.views,
    0
);
console.log(sumViews); // 5084

使用 .reduce() 方法可以用于展平一個數組,當然已經有很多方法可以做到這一點,這就是其中的方法之一。

const flattened = [
    [0, 1],
    [2, 3],
    [4, 5],
].reduce((accumulator, current) => accumulator.concat(current), []);
console.log(flattened); // [ 0, 1, 2, 3, 4, 5 ]

當需要通過操作其值將數組向下轉換為單個值時,可以使用 .reduce() 方法

接下來我們來看一個奇葩需求,出于某種原因,需要一個包含所有用戶全名的新數組(他們的姓,加上他們的名字),但只有當他們是 20 多歲,并且他們的全名是 3 個字的時候才需要。不要問我們為什么需要這么奇葩的數據子集,產品經理問了,我們很樂意幫忙^_^

const users = [
    {
        firstName: "堅",
        lastName: "孫",
        age: 37,
    },
    {
        firstName: "策",
        lastName: "孫",
        age: 21,
    },
    {
        firstName: "葛亮",
        lastName: "諸",
        age: 28,
    },
    {
        firstName: "備",
        lastName: "劉",
        age: 44,
    },
    {
        firstName: "統",
        lastName: "龐",
        age: 22,
    },
    {
        firstName: "維",
        lastName: "姜",
        age: 19,
    },
    {
        firstName: "伯溫",
        lastName: "劉",
        age: 22,
    },
];
const getFullName = (user) => `${user.lastName}${user.firstName}`;
const filterByAge = (user) => user.age >= 20 && user.age < 30;

// 常規實現
const getFilterResult = users
    //  第一步篩選年齡20-30之間的用戶
    .filter((user) => filterByAge(user))
    //  拼接全名
    .map((user) => getFullName(user))
    //  篩選
    .filter((fullName) => fullName.length === 3);

console.log(getFilterResult);   // [ '諸葛亮', '劉伯溫' ]

// 迭代方式實現
const iterationsFilterResult = (arrayResult, currentUser) => {
    const fullname = getFullName(currentUser);
    if (filterByAge(currentUser) && fullname.length === 3) {
        arrayResult.push(fullname);
    }
    return arrayResult;
};
console.log(users.reduce(iterationsFilterResult, []));  // [ '諸葛亮', '劉伯溫' ]

11. Array.slice()

slice() 方法將數組部分的副本返回到新的數組對象中。這個對象是從 startend 選擇的。需要注意的是,此方法不會修改原始數組。此外,如果向其中一個數組添加新元素,則另一個數組不會受到影響。

語法

arr.slice([begin[, end]])

slice() 方法的參數是數組的開始和結束索引。

  • start:是一個從 0 開始的索引,用于開始復制數組的一部分。如果未定義,start 的默認值為 0。如果 start 大于數組的索引范圍, slice() 方法將返回一個空數組。此外,start 還可以使用負索引。 slice(-1) 提取數組的最后一個元素。
  • end:可選,如果 slice() 函數中只有一個參數,那就是 start。如果省略, slice() 方法從數組的末尾開始提取。如果 end 大于數組的長度,slice() 一直提取到數組的末尾,只是在它被省略的情況下。end 是提取此索引之前的元素,不包括索引 end 在內。因此,索引的最后一個元素不包含在數組的副本中。例如,slice(1,3) 提取是數組的第二個和第三個元素,即從數組的索引 1 開始,包含索引 1 的值到索引 3 之間的數組,但不包含索引為 3 的元素。

返回值

一個含有被提取元素的新數組。

實例

下面實例代碼是復制數組 arrNumbers 從索引 0 開始到索引 3 之間的元素,不包含索引為 3 的元素 。

const arrNumbers = [1,3,5,6,7];
console.log(arrNumbers.slice(0, 3)); // [ 1, 3, 5 ]

可以用于復制數組,上面示例是slice() 函數的基本功能,沒有參數的數組復制原始數組。有時,可能想要更新數組中的某些元素。但是,可能需要保護原始數組中的元素,可以使用 slice() 創建原始數組的淺復制

const arrNumbers = [1, 2, 3, 4, 5, 6];
const copyNumbers = arrNumbers.slice();
console.log(copyNumbers); // [ 1, 2, 3, 4, 5, 6 ]
copyNumbers[1] = 0;
console.log(copyNumbers); // [ 1, 0, 3, 4, 5, 6 ]
console.log(arrNumbers); // [ 1, 2, 3, 4, 5, 6 ]

12. Array.splice()

splice() 方法可以從數組中排除舊值,然后將新值插入數組。使用 splice() 后,將獲得兩個數組,第一個是排除的數組,第二個是編輯過的新數組。 需要注意的是,此方法會改變原數組

語法

splice(start[, deleteCount[, item1[, item2[, ...]]]]) 方法的參數是從數組 start 索引開始,刪除 deleteCount 個元素,返回刪除元素組成的數組

  • start: 是一個從 0 開始的索引,用于指定修改的開始索引位置。如果未定義,start 的默認值為 0。如果 start 大于數組的索引范圍,則從數組末尾開始添加內容。此外,start 還可以使用負索引,則表示從數組末位開始的第幾位(從 -1 計數,這意味著 -n 就是倒數第 n 個元素,其實就是等價于 array.length-n

  • deleteCount:是可選的,整數,表示要移除的數組元素的個數。如果 deleteCount 大于 start 之后的元素的總數,則從 start 后面的元素都將被刪除(含第 start 位)。如果 deleteCount 被省略了,或者它的值大于等于 array.length - start(也就是說,如果它大于或者等于 start 之后的所有元素的數),那么 start 之后數組的所有元素都會被刪除。如果 deleteCount0 或者負數,則不移除元素,這種情況下,至少應添加一個新元素。

  • tem1, item2, ... :可選,要添加進數組的元素,從 start 位置開始。如果不指定,splice() 則將只刪除數組元素。添加進去的元素的位置位于開始刪除索引后,結束于結束索引前。

返回值

由被刪除的元素組成的一個數組。如果只刪除了一個元素,則返回只包含一個元素的數組。如果沒有刪除元素,則返回空數組。

實例

const arrNumbers = [1, 3, 5, 6, 7];
console.log(arrNumbers.splice(0, 3, 8, 9)); // [ 1, 3, 5 ]
console.log(arrNumbers); // [ 8, 9, 6, 7 ]

// 刪除元素
const arrNumbers = [1, 3, 5, 6, 7];
console.log(arrNumbers.splice(0)); // [ 1, 3, 5, 6, 7 ]
console.log(arrNumbers); // [] 

// 在數組中插入元素
const ColorNames = (start, deleteCount, arr) => {
    arr.splice(start, deleteCount, "Pink", "Black", "Green");
    return arr;
};
// 數組后
console.log(ColorNames(2, 0, ["Golden", "Brown"])); // [ 'Golden', 'Brown', 'Pink', 'Black', 'Green' ]
// 數組前
console.log(ColorNames(0, 0, ["Golden", "Brown"])); // [ 'Pink', 'Black', 'Green', 'Golden', 'Brown' ]

13. Array.sort()

.sort() 對數組進行排序,不考慮其數據類型:無論是編號數組、字符串數組還是復雜的對象數組。需要注意的一點是,.sort() 方法不會返回新的排序數組,而是更改原始數組

語法

arr.sort([compareFunction])
  • compareFunction:可選,用來指定按某種順序進行排列的函數。如果省略,元素按照轉換為的字符串的各個字符的 Unicode 位點進行排序。
    • firstEl:第一個用于比較的元素。
    • secondEl:第二個用于比較的元素。

對于 compareFunction ,返回值的不同將影響數組的排序,如下:

  • 如果 compareFunction(a, b) 返回 0,則表示 a 排在 b 前面
  • 如果compareFunction(a, b) 返回大于 0 的值,則表示 b 排在 a 前面
  • 如果 compareFunction(a, b) 返回小于 0 的值 ,則表示 a 排在 b 前面

返回值

排序后的數組,不會返回新的排序數組,而是更改原始數組。

實例

const months = ["Nov", "Feb", "Jan", "Dec"];
const sorted = months.sort();

console.log(months); // [ 'Dec', 'Feb', 'Jan', 'Nov' ]
console.log(sorted); // [ 'Dec', 'Feb', 'Jan', 'Nov' ]

總結

JavaScript 提供了大量不同的處理數組的方法,本文介紹的內容基本囊括了可能用到的數組方法,而對于.concat() 方法,幾乎用擴展符替代了,如 [...array1,...array2] 。數組操作是編程世界里面最基本的操作,有必要熟練掌握。