索引集合
本章介紹按索引值排序的資料集合。這包括陣列和類陣列結構,例如 Array 物件和 TypedArray 物件。
陣列是值的有序列表,你可以透過名稱和索引來引用它們。
例如,考慮一個名為 emp 的陣列,它包含按數字員工編號索引的員工姓名。因此,emp[0] 將是零號員工,emp[1] 是一號員工,依此類推。
JavaScript 沒有顯式的陣列資料型別。但是,你可以使用預定義的 Array 物件及其方法在你的應用程式中處理陣列。Array 物件具有多種運算元組的方法,例如連線、反轉和排序。它有一個用於確定陣列長度的屬性以及其他用於正則表示式的屬性。
本文將重點介紹陣列,但許多相同的概念也適用於型別化陣列,因為陣列和型別化陣列共享許多相似的方法。有關型別化陣列的更多資訊,請參閱型別化陣列指南。
建立陣列
以下語句建立等效陣列
const arr1 = new Array(element0, element1, /* …, */ elementN);
const arr2 = Array(element0, element1, /* …, */ elementN);
const arr3 = [element0, element1, /* …, */ elementN];
element0, element1, ..., elementN 是陣列元素的列表。指定這些值後,陣列將使用它們作為陣列的元素進行初始化。陣列的 length 屬性設定為引數的數量。
方括號語法被稱為“陣列字面量”或“陣列初始化器”。它比其他形式的陣列建立更短,因此通常更受推薦。有關詳細資訊,請參閱陣列字面量。
要建立非零長度但沒有任何項的陣列,可以使用以下任一方法:
// This...
const arr1 = new Array(arrayLength);
// … results in the same array as this
const arr2 = Array(arrayLength);
// This has exactly the same effect
const arr3 = [];
arr3.length = arrayLength;
注意:在上述程式碼中,arrayLength 必須是一個 Number。否則,將建立一個包含單個元素(提供的值)的陣列。呼叫 arr.length 將返回 arrayLength,但陣列不包含任何元素。一個 for...in 迴圈將不會在陣列上找到任何屬性。
除了如上所示的新定義變數外,陣列還可以作為新物件或現有物件的屬性進行賦值
const obj = {};
// …
obj.prop = [element0, element1, /* …, */ elementN];
// OR
const obj = { prop: [element0, element1, /* …, */ elementN] };
如果你想用單個元素初始化一個數組,並且該元素恰好是一個 Number,你必須使用方括號語法。當一個單個 Number 值傳遞給 Array() 建構函式或函式時,它被解釋為 arrayLength,而不是單個元素。
這將建立一個只包含一個元素(數字 42)的陣列。
const arr = [42];
這將建立一個沒有元素且 arr.length 設定為 42 的陣列。
const arr = Array(42);
這等價於
const arr = [];
arr.length = 42;
如果 N 是一個非整數且其小數部分不為零的數字,呼叫 Array(N) 會導致 RangeError。以下示例說明了此行為。
const arr = Array(9.3); // RangeError: Invalid array length
如果你的程式碼需要建立包含任意資料型別單個元素的陣列,使用陣列字面量會更安全。或者,先建立一個空陣列,然後再新增單個元素。
你也可以使用 Array.of 靜態方法建立包含單個元素的陣列。
const arr = Array.of(9.3); // arr contains only one element 9.3
引用陣列元素
由於元素也是屬性,你可以使用屬性訪問器訪問它們。假設你定義了以下陣列:
const myArray = ["Wind", "Rain", "Fire"];
你可以將陣列的第一個元素稱為 myArray[0],第二個元素稱為 myArray[1],依此類推……元素的索引從零開始。
注意:你也可以使用屬性訪問器訪問陣列的其他屬性,就像使用物件一樣。
const arr = ["one", "two", "three"];
arr[2]; // three
arr["length"]; // 3
填充陣列
你可以透過給陣列元素賦值來填充陣列。例如:
const emp = [];
emp[0] = "Casey Jones";
emp[1] = "Phil Lesh";
emp[2] = "August West";
注意:如果你在上述程式碼中向陣列運算子提供一個非整數值,則會在表示陣列的物件中建立一個屬性,而不是陣列元素。
const arr = [];
arr[3.4] = "Oranges";
console.log(arr.length); // 0
console.log(Object.hasOwn(arr, 3.4)); // true
你也可以在建立陣列時填充它
const myArray = new Array("Hello", myVar, 3.14159);
// OR
const myArray = ["Mango", "Apple", "Orange"];
理解長度
在實現層面,JavaScript 的陣列實際上將其元素作為標準物件屬性儲存,並使用陣列索引作為屬性名。
length 屬性是特殊的。如果存在最後一個元素,其值始終是一個大於該元素索引的正整數。(在下面的示例中,'Dusty' 的索引是 30,因此 cats.length 返回 30 + 1)。
請記住,JavaScript 陣列索引是 0-based:它們從 0 開始,而不是 1。這意味著 length 屬性將比陣列中儲存的最高索引大一
const cats = [];
cats[30] = ["Dusty"];
console.log(cats.length); // 31
你也可以給 length 屬性賦值。
寫入一個比儲存項數量短的值會截斷陣列。寫入 0 會完全清空陣列
const cats = ["Dusty", "Misty", "Twiggy"];
console.log(cats.length); // 3
cats.length = 2;
console.log(cats); // [ 'Dusty', 'Misty' ] - Twiggy has been removed
cats.length = 0;
console.log(cats); // []; the cats array is empty
cats.length = 3;
console.log(cats); // [ <3 empty items> ]
迭代陣列
一種常見的操作是遍歷陣列的值,並以某種方式處理每個值,如下所示:
const colors = ["red", "green", "blue"];
for (let i = 0; i < colors.length; i++) {
console.log(colors[i]);
}
如果你知道陣列中的任何元素在布林上下文中都不會評估為 false(例如,如果你的陣列只包含 DOM 節點),你可以使用更高效的慣用語:
const divs = document.getElementsByTagName("div");
for (let i = 0, div; (div = divs[i]); i++) {
/* Process div in some way */
}
這避免了檢查陣列長度的開銷,並確保 div 變數在每次迴圈時都被重新分配給當前項,從而增加了便利性。
forEach() 方法提供了另一種遍歷陣列的方式:
const colors = ["red", "green", "blue"];
colors.forEach((color) => console.log(color));
// red
// green
// blue
傳遞給 forEach 的函式會為陣列中的每個項執行一次,並將陣列項作為引數傳遞給該函式。未賦值的值不會在 forEach 迴圈中迭代。
請注意,當陣列定義時省略的陣列元素在透過 forEach 迭代時不會被列出,但如果手動將 undefined 賦給該元素,則會被列出。
const sparseArray = ["first", "second", , "fourth"];
sparseArray.forEach((element) => {
console.log(element);
});
// Logs:
// first
// second
// fourth
if (sparseArray[2] === undefined) {
console.log("sparseArray[2] is undefined"); // true
}
const nonsparseArray = ["first", "second", undefined, "fourth"];
nonsparseArray.forEach((element) => {
console.log(element);
});
// Logs:
// first
// second
// undefined
// fourth
由於 JavaScript 陣列元素作為標準物件屬性儲存,因此不建議使用 for...in 迴圈迭代 JavaScript 陣列,因為會列出普通元素和所有可列舉屬性。
陣列方法
Array 物件有以下方法:
concat() 方法連線兩個或多個數組並返回一個新陣列。
let myArray = ["1", "2", "3"];
myArray = myArray.concat("a", "b", "c");
// myArray is now ["1", "2", "3", "a", "b", "c"]
join() 方法將陣列的所有元素連線成一個字串。
const myArray = ["Wind", "Rain", "Fire"];
const list = myArray.join(" - "); // list is "Wind - Rain - Fire"
push() 方法向陣列末尾新增一個或多個元素,並返回陣列的最終 length。
const myArray = ["1", "2"];
myArray.push("3"); // myArray is now ["1", "2", "3"]
pop() 方法從陣列中移除最後一個元素並返回該元素。
const myArray = ["1", "2", "3"];
const last = myArray.pop();
// myArray is now ["1", "2"], last = "3"
shift() 方法從陣列中移除第一個元素並返回該元素。
const myArray = ["1", "2", "3"];
const first = myArray.shift();
// myArray is now ["2", "3"], first is "1"
unshift() 方法向陣列前端新增一個或多個元素,並返回陣列的新長度。
const myArray = ["1", "2", "3"];
myArray.unshift("4", "5");
// myArray becomes ["4", "5", "1", "2", "3"]
slice() 方法提取陣列的一部分並返回一個新陣列。
let myArray = ["a", "b", "c", "d", "e"];
myArray = myArray.slice(1, 4); // [ "b", "c", "d"]
// starts at index 1 and extracts all elements
// until index 3
at() 方法返回陣列中指定索引處的元素,如果索引超出範圍則返回 undefined。它主要用於訪問陣列末尾元素的負索引。
const myArray = ["a", "b", "c", "d", "e"];
myArray.at(-2); // "d", the second-last element of myArray
splice() 方法從陣列中移除元素並(可選地)替換它們。它返回從陣列中移除的項。
const myArray = ["1", "2", "3", "4", "5"];
myArray.splice(1, 3, "a", "b", "c", "d");
// myArray is now ["1", "a", "b", "c", "d", "5"]
// This code started at index one (or where the "2" was),
// removed 3 elements there, and then inserted all consecutive
// elements in its place.
reverse() 方法將陣列元素就地轉置:第一個陣列元素變為最後一個,最後一個變為第一個。它返回對陣列的引用。
const myArray = ["1", "2", "3"];
myArray.reverse();
// transposes the array so that myArray = ["3", "2", "1"]
flat() 方法返回一個新陣列,其中所有子陣列元素都遞迴地連線到其中,直到指定深度。
let myArray = [1, 2, [3, 4]];
myArray = myArray.flat();
// myArray is now [1, 2, 3, 4], since the [3, 4] subarray is flattened
sort() 方法將陣列元素就地排序,並返回對陣列的引用。
const myArray = ["Wind", "Rain", "Fire"];
myArray.sort();
// sorts the array so that myArray = ["Fire", "Rain", "Wind"]
sort() 還可以接受一個回撥函式來決定如何比較陣列元素。回撥函式以兩個引數呼叫,這兩個引數是陣列中的兩個值。該函式比較這兩個值並返回一個正數、負數或零,指示這兩個值的順序。例如,以下程式碼將按字串的最後一個字母對陣列進行排序:
const sortFn = (a, b) => {
if (a[a.length - 1] < b[b.length - 1]) {
return -1; // Negative number => a < b, a comes before b
} else if (a[a.length - 1] > b[b.length - 1]) {
return 1; // Positive number => a > b, a comes after b
}
return 0; // Zero => a = b, a and b keep their original order
};
myArray.sort(sortFn);
// sorts the array so that myArray = ["Wind","Fire","Rain"]
- 如果
a在排序系統中小於b,則返回-1(或任何負數) - 如果
a在排序系統中大於b,則返回1(或任何正數) - 如果
a和b被認為是等效的,則返回0。
indexOf() 方法在陣列中搜索 searchElement 並返回第一個匹配項的索引。
const a = ["a", "b", "a", "b", "a"];
console.log(a.indexOf("b")); // 1
// Now try again, starting from after the last match
console.log(a.indexOf("b", 2)); // 3
console.log(a.indexOf("z")); // -1, because 'z' was not found
lastIndexOf() 方法類似於 indexOf,但從末尾開始向後搜尋。
const a = ["a", "b", "c", "d", "a", "b"];
console.log(a.lastIndexOf("b")); // 5
// Now try again, starting from before the last match
console.log(a.lastIndexOf("b", 4)); // 1
console.log(a.lastIndexOf("z")); // -1
forEach() 方法在每個陣列項上執行 callback 並返回 undefined。
const a = ["a", "b", "c"];
a.forEach((element) => {
console.log(element);
});
// Logs:
// a
// b
// c
接受回撥的 forEach 方法(以及下面的其他方法)被稱為迭代方法,因為它們以某種方式遍歷整個陣列。每個方法都接受一個可選的第二個引數,稱為 thisArg。如果提供了 thisArg,則 thisArg 成為回撥函式體內部 this 關鍵字的值。如果未提供,與其他在顯式物件上下文之外呼叫函式的情況一樣,當函式不是嚴格模式時,this 將引用全域性物件(window、globalThis 等),當函式是嚴格模式時,this 將是 undefined。
注意:上面介紹的 sort() 方法不是迭代方法,因為它的回撥函式僅用於比較,並且可能不會根據元素順序以特定順序呼叫。sort() 也不接受 thisArg 引數。
map() 方法返回一個新陣列,其中包含在每個陣列項上執行 callback 的返回值。
const a1 = ["a", "b", "c"];
const a2 = a1.map((item) => item.toUpperCase());
console.log(a2); // ['A', 'B', 'C']
flatMap() 方法執行 map(),然後執行深度為 1 的 flat()。
const a1 = ["a", "b", "c"];
const a2 = a1.flatMap((item) => [item.toUpperCase(), item.toLowerCase()]);
console.log(a2); // ['A', 'a', 'B', 'b', 'C', 'c']
filter() 方法返回一個新陣列,其中包含 callback 返回 true 的項。
const a1 = ["a", 10, "b", 20, "c", 30];
const a2 = a1.filter((item) => typeof item === "number");
console.log(a2); // [10, 20, 30]
find() 方法返回 callback 返回 true 的第一個項。
const a1 = ["a", 10, "b", 20, "c", 30];
const i = a1.find((item) => typeof item === "number");
console.log(i); // 10
findLast() 方法返回 callback 返回 true 的最後一個項。
const a1 = ["a", 10, "b", 20, "c", 30];
const i = a1.findLast((item) => typeof item === "number");
console.log(i); // 30
findIndex() 方法返回 callback 返回 true 的第一個項的索引。
const a1 = ["a", 10, "b", 20, "c", 30];
const i = a1.findIndex((item) => typeof item === "number");
console.log(i); // 1
findLastIndex() 方法返回 callback 返回 true 的最後一個項的索引。
const a1 = ["a", 10, "b", 20, "c", 30];
const i = a1.findLastIndex((item) => typeof item === "number");
console.log(i); // 5
every() 方法在陣列中每個項的 callback 都返回 true 時返回 true。
function isNumber(value) {
return typeof value === "number";
}
const a1 = [1, 2, 3];
console.log(a1.every(isNumber)); // true
const a2 = [1, "2", 3];
console.log(a2.every(isNumber)); // false
some() 方法在陣列中至少一個項的 callback 返回 true 時返回 true。
function isNumber(value) {
return typeof value === "number";
}
const a1 = [1, 2, 3];
console.log(a1.some(isNumber)); // true
const a2 = [1, "2", 3];
console.log(a2.some(isNumber)); // true
const a3 = ["1", "2", "3"];
console.log(a3.some(isNumber)); // false
reduce() 方法對陣列中的每個值應用 callback(accumulator, currentValue, currentIndex, array),以將項列表縮減為單個值。reduce 函式返回 callback 函式返回的最終值。
如果指定了 initialValue,則 callback 將以 initialValue 作為第一個引數值,並以陣列中第一個項的值作為第二個引數值進行呼叫。
如果未指定 initialValue,則 callback 的前兩個引數值將是陣列的第一個和第二個元素。在每次後續呼叫中,第一個引數的值將是 callback 在前一次呼叫中返回的任何值,第二個引數的值將是陣列中的下一個值。
如果 callback 需要訪問正在處理的項的索引,或訪問整個陣列,它們都可以作為可選引數提供。
const a = [10, 20, 30];
const total = a.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0,
);
console.log(total); // 60
reduceRight() 方法的工作方式與 reduce() 類似,但從最後一個元素開始。
reduce 和 reduceRight 是迭代陣列方法中最不明顯的。它們應該用於遞迴地組合兩個值以將序列縮減為單個值的演算法。
陣列轉換
你可以在陣列和其他資料結構之間來回轉換。
對陣列元素進行分組
可以使用 Object.groupBy() 方法對陣列的元素進行分組,使用一個測試函式,該函式返回一個字串,指示當前元素的組。
這裡我們有一個包含具有 name 和 type 的“食物”物件的庫存陣列。
const inventory = [
{ name: "asparagus", type: "vegetables" },
{ name: "bananas", type: "fruit" },
{ name: "goat", type: "meat" },
{ name: "cherries", type: "fruit" },
{ name: "fish", type: "meat" },
];
要使用 Object.groupBy(),你需要提供一個回撥函式,該函式將以當前元素(可選地還有當前索引和陣列)作為引數呼叫,並返回一個字串,指示該元素的組。
下面的程式碼使用箭頭函式返回每個陣列元素的 type(這使用了函式引數的物件解構語法來從傳入的物件中解構 type 元素)。結果是一個物件,其屬性以回撥函式返回的唯一字串命名。每個屬性都被分配一個包含該組中元素的陣列。
const result = Object.groupBy(inventory, ({ type }) => type);
console.log(result);
// Logs
// {
// vegetables: [{ name: 'asparagus', type: 'vegetables' }],
// fruit: [
// { name: 'bananas', type: 'fruit' },
// { name: 'cherries', type: 'fruit' }
// ],
// meat: [
// { name: 'goat', type: 'meat' },
// { name: 'fish', type: 'meat' }
// ]
// }
請注意,返回的物件引用的是與原始陣列相同的元素(而非深複製)。更改這些元素的內部結構將在原始陣列和返回物件中反映出來。
如果你不能使用字串作為鍵,例如,如果用於分組的資訊與可能更改的物件相關聯,那麼你可以使用 Map.groupBy()。這與 Object.groupBy() 非常相似,不同之處在於它將陣列的元素分組到一個 Map 中,該 Map 可以使用任意值(物件或原始值)作為鍵。
稀疏陣列
陣列可以包含“空槽”,這與填充了 undefined 值的槽不同。空槽可以透過以下方式之一建立:
// Array constructor:
const a = Array(5); // [ <5 empty items> ]
// Consecutive commas in array literal:
const b = [1, 2, , , 5]; // [ 1, 2, <2 empty items>, 5 ]
// Directly setting a slot with index greater than array.length:
const c = [1, 2];
c[4] = 5; // [ 1, 2, <2 empty items>, 5 ]
// Elongating an array by directly setting .length:
const d = [1, 2];
d.length = 5; // [ 1, 2, <3 empty items> ]
// Deleting an element:
const e = [1, 2, 3, 4, 5];
delete e[2]; // [ 1, 2, <1 empty item>, 4, 5 ]
在某些操作中,空槽的行為就像它們被 undefined 填充一樣。
const arr = [1, 2, , , 5]; // Create a sparse array
// Indexed access
console.log(arr[2]); // undefined
// For...of
for (const i of arr) {
console.log(i);
}
// Logs: 1 2 undefined undefined 5
// Spreading
const another = [...arr]; // "another" is [ 1, 2, undefined, undefined, 5 ]
但在其他操作中(最顯著的是陣列迭代方法),空槽會被跳過。
const mapped = arr.map((i) => i + 1); // [ 2, 3, <2 empty items>, 6 ]
arr.forEach((i) => console.log(i)); // 1 2 5
const filtered = arr.filter(() => true); // [ 1, 2, 5 ]
const hasFalsy = arr.some((k) => !k); // false
// Property enumeration
const keys = Object.keys(arr); // [ '0', '1', '4' ]
for (const key in arr) {
console.log(key);
}
// Logs: '0' '1' '4'
// Spreading into an object uses property enumeration, not the array's iterator
const objectSpread = { ...arr }; // { '0': 1, '1': 2, '4': 5 }
有關陣列方法如何處理稀疏陣列的完整列表,請參閱Array 參考頁面。
多維陣列
陣列可以巢狀,這意味著一個數組可以包含另一個數組作為元素。利用 JavaScript 陣列的這一特性,可以建立多維陣列。
以下程式碼建立一個二維陣列。
const a = new Array(4);
for (let i = 0; i < 4; i++) {
a[i] = new Array(4);
for (let j = 0; j < 4; j++) {
a[i][j] = `[${i}, ${j}]`;
}
}
此示例建立了一個具有以下行的陣列:
Row 0: [0, 0] [0, 1] [0, 2] [0, 3] Row 1: [1, 0] [1, 1] [1, 2] [1, 3] Row 2: [2, 0] [2, 1] [2, 2] [2, 3] Row 3: [3, 0] [3, 1] [3, 2] [3, 3]
使用陣列儲存其他屬性
陣列也可以像物件一樣使用,用於儲存相關資訊。
const arr = [1, 2, 3];
arr.property = "value";
console.log(arr.property); // "value"
例如,當陣列是正則表示式與字串匹配的結果時,陣列會返回提供匹配資訊的屬性和元素。陣列是 RegExp.prototype.exec()、String.prototype.match() 和 String.prototype.split() 的返回值。有關將陣列與正則表示式一起使用的資訊,請參閱正則表示式。
使用類陣列物件
一些 JavaScript 物件,例如 NodeList(由 document.getElementsByTagName() 返回)或在函式體內可用的 arguments 物件,表面上看起來和行為都像陣列,但並不共享它們的所有方法。arguments 物件提供了 length 屬性,但並未實現像 forEach() 這樣的陣列方法。
陣列方法不能直接在類陣列物件上呼叫。
function printArguments() {
arguments.forEach((item) => {
console.log(item);
}); // TypeError: arguments.forEach is not a function
}
但你可以使用 Function.prototype.call() 間接呼叫它們。
function printArguments() {
Array.prototype.forEach.call(arguments, (item) => {
console.log(item);
});
}
陣列原型方法也可以用於字串,因為它們以類似於陣列的方式提供對其字元的順序訪問
Array.prototype.forEach.call("a string", (chr) => {
console.log(chr);
});