描述
在 JavaScript 中,陣列不是原始值,而是具有以下核心特徵的 Array 物件:
陣列索引
Array 物件不能使用任意字串作為元素索引(如在關聯陣列中),而必須使用非負整數(或其各自的字串形式)。透過非整數設定或訪問不會從陣列列表本身設定或檢索元素,而是會設定或訪問與該陣列的物件屬性集合關聯的變數。陣列的物件屬性和陣列元素列表是分開的,陣列的遍歷和變異操作不能應用於這些命名屬性。
陣列元素是物件屬性,就像 toString 是屬性一樣(但具體來說,toString() 是一個方法)。然而,嘗試按如下方式訪問陣列元素會丟擲語法錯誤,因為屬性名無效:
arr.0; // a syntax error
JavaScript 語法要求以數字開頭的屬性使用方括號表示法而不是點表示法訪問。也可以引用陣列索引(例如,years['2'] 而不是 years[2]),儘管通常不需要。
years[2] 中的 2 透過隱式 toString 轉換被 JavaScript 引擎強制轉換為字串。因此,'2' 和 '02' 將指向 years 物件上的兩個不同槽位,以下示例可能為 true:
console.log(years["2"] !== years["02"]);
只有 years['2'] 是實際的陣列索引。years['02'] 是一個任意字串屬性,在陣列迭代中不會被訪問。
長度與數字屬性的關係
JavaScript 陣列的length 屬性和數字屬性是相互關聯的。
在呼叫時,一些內建陣列方法(例如,join()、slice()、indexOf() 等)會考慮陣列的length 屬性值。
其他方法(例如,push()、splice() 等)也會導致陣列的length 屬性更新。
const fruits = [];
fruits.push("banana", "apple", "peach");
console.log(fruits.length); // 3
當在 JavaScript 陣列上設定屬性時,如果該屬性是有效的陣列索引且該索引超出陣列當前邊界,引擎將相應地更新陣列的length 屬性:
fruits[5] = "mango";
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6
增加length 會透過新增空槽位來擴充套件陣列,而不會建立任何新元素——甚至不是 undefined。
fruits.length = 10;
console.log(fruits); // ['banana', 'apple', 'peach', empty x 2, 'mango', empty x 4]
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 10
console.log(fruits[8]); // undefined
然而,減少length 屬性會刪除元素。
fruits.length = 2;
console.log(Object.keys(fruits)); // ['0', '1']
console.log(fruits.length); // 2
這在length 頁面上有進一步解釋。
陣列方法和空槽位
在遇到稀疏陣列中的空槽位時,陣列方法有不同的行為。一般來說,較舊的方法(例如 forEach)將空槽位與包含 undefined 的索引區別對待。
對空槽位進行特殊處理的方法包括:concat()、copyWithin()、every()、filter()、flat()、flatMap()、forEach()、indexOf()、lastIndexOf()、map()、reduce()、reduceRight()、reverse()、slice()、some()、sort() 和 splice()。像 forEach 這樣的迭代方法根本不會訪問空槽位。其他方法,例如 concat、copyWithin 等,在複製時會保留空槽位,因此最終陣列仍然是稀疏的。
const colors = ["red", "yellow", "blue"];
colors[5] = "purple";
colors.forEach((item, index) => {
console.log(`${index}: ${item}`);
});
// Output:
// 0: red
// 1: yellow
// 2: blue
// 5: purple
colors.reverse(); // ['purple', empty × 2, 'blue', 'yellow', 'red']
較新的方法(例如 keys)不會特殊處理空槽位,並將它們視為包含 undefined。將空槽位與 undefined 元素混淆的方法包括:entries()、fill()、find()、findIndex()、findLast()、findLastIndex()、includes()、join()、keys()、toLocaleString()、toReversed()、toSorted()、toSpliced()、values() 和 with()。
const colors = ["red", "yellow", "blue"];
colors[5] = "purple";
const iterator = colors.keys();
for (const key of iterator) {
console.log(`${key}: ${colors[key]}`);
}
// Output
// 0: red
// 1: yellow
// 2: blue
// 3: undefined
// 4: undefined
// 5: purple
const newColors = colors.toReversed(); // ['purple', undefined, undefined, 'blue', 'yellow', 'red']
複製方法和變異方法
有些方法不會改變呼叫該方法的現有陣列,而是返回一個新陣列。它們透過首先構造一個新陣列,然後用元素填充它來實現。複製總是淺層的——方法永遠不會複製超出最初建立的陣列的任何內容。原始陣列的元素按以下方式複製到新陣列中:
- 物件:物件引用被複制到新陣列中。原始陣列和新陣列都引用相同的物件。也就是說,如果引用的物件被修改,則更改對新陣列和原始陣列都可見。
- 字串、數字和布林值等原始型別(不是
String、Number和Boolean物件):它們的值被複制到新陣列中。
其他方法會改變呼叫該方法的陣列,在這種情況下,它們的返回值因方法而異:有時是同一陣列的引用,有時是新陣列的長度。
以下方法透過訪問 this.constructor[Symbol.species] 來確定要使用的建構函式,從而建立新陣列:concat()、filter()、flat()、flatMap()、map()、slice() 和 splice()(用於構造返回的已刪除元素陣列)。
以下方法總是使用 Array 基本建構函式建立新陣列:toReversed()、toSorted()、toSpliced() 和 with()。
下表列出了會改變原始陣列的方法,以及相應的非變異替代方法:
| 變異方法 | 非變異替代方法 |
|---|---|
copyWithin() |
沒有單方法替代 |
fill() |
沒有單方法替代 |
pop() |
slice(0, -1) |
push(v1, v2) |
concat([v1, v2]) |
reverse() |
toReversed() |
shift() |
slice(1) |
sort() |
toSorted() |
splice() |
toSpliced() |
unshift(v1, v2) |
toSpliced(0, 0, v1, v2) |
將變異方法轉換為非變異替代方法的簡單方法是,首先使用展開語法或slice() 來建立副本:
arr.copyWithin(0, 1, 2); // mutates arr
const arr2 = arr.slice().copyWithin(0, 1, 2); // does not mutate arr
const arr3 = [...arr].copyWithin(0, 1, 2); // does not mutate arr
迭代方法
許多陣列方法都接受回撥函式作為引數。回撥函式按順序呼叫,並且陣列中的每個元素最多呼叫一次,回撥函式的返回值用於確定方法的返回值。它們都具有相同的簽名:
method(callbackFn, thisArg)
其中 callbackFn 接受三個引數:
callbackFn 應該返回什麼取決於所呼叫的陣列方法。
呼叫 callbackFn 時,thisArg 引數(預設為 undefined)將用作 this 值。callbackFn 最終可觀察到的 this 值根據常規規則確定:如果 callbackFn 不是嚴格模式,原始 this 值將封裝為物件,並且 undefined/null 將替換為globalThis。thisArg 引數與使用箭頭函式定義的任何 callbackFn 無關,因為箭頭函式沒有自己的 this 繫結。
傳遞給 callbackFn 的 array 引數在迭代期間讀取另一個索引時最有用,因為您可能並不總是有一個現有變數引用當前陣列。您通常不應該在迭代期間改變陣列(請參閱迭代方法中改變初始陣列),但您也可以使用此引數來執行此操作。在像 map()、filter() 和 flatMap() 這樣的方法中,array 引數不是正在構建的陣列——無法從回撥函式訪問正在構建的陣列。
所有迭代方法都是複製的和通用的,儘管它們在處理空槽位時行為不同。
以下方法是迭代方法:every()、filter()、find()、findIndex()、findLast()、findLastIndex()、flatMap()、forEach()、map() 和 some()。
特別是,every()、find()、findIndex()、findLast()、findLastIndex() 和 some() 並不總是對每個元素呼叫 callbackFn——它們會在確定返回值後立即停止迭代。
reduce() 和 reduceRight() 方法也接受回撥函式,並且對於陣列中的每個元素最多執行一次,但它們的簽名與典型的迭代方法略有不同(例如,它們不接受 thisArg)。
sort() 方法也接受回撥函式,但它不是迭代方法。它會就地改變陣列,不接受 thisArg,並且可能會在同一索引上多次呼叫回撥。
迭代方法按以下方式迭代陣列(省略了許多技術細節):
function method(callbackFn, thisArg) {
const length = this.length;
for (let i = 0; i < length; i++) {
if (i in this) {
const result = callbackFn.call(thisArg, this[i], i, this);
// Do something with result; maybe return early
}
}
}
請注意以下事項:
- 並非所有方法都執行
i in this測試。find、findIndex、findLast和findLastIndex方法不執行此測試,但其他方法執行。 length在迴圈開始前被記憶。這會影響迭代期間的插入和刪除處理方式(參見迭代方法中改變初始陣列)。- 該方法不記憶陣列內容,因此如果在迭代期間修改任何索引,則可能會觀察到新值。
- 上面的程式碼按索引升序迭代陣列。某些方法按索引降序迭代(
for (let i = length - 1; i >= 0; i--)):reduceRight()、findLast()和findLastIndex()。 reduce和reduceRight具有略微不同的簽名,並且並不總是從第一個/最後一個元素開始。
通用陣列方法
陣列方法總是通用的——它們不訪問陣列物件的任何內部資料。它們只通過 length 屬性和索引元素訪問陣列元素。這意味著它們也可以在類陣列物件上呼叫。
const arrayLike = {
0: "a",
1: "b",
length: 2,
};
console.log(Array.prototype.join.call(arrayLike, "+")); // 'a+b'
長度屬性的標準化
length 屬性被轉換為整數,然後鉗制到 0 和 253 - 1 之間的範圍。NaN 變為 0,因此即使 length 不存在或為 undefined,它的行為也像其值為 0 一樣。
該語言避免將 length 設定為不安全整數。如果 length 將設定為大於 253 - 1 的數字,則所有內建方法都將丟擲 TypeError。然而,由於陣列的length 屬性在設定為大於 232 - 1 時會丟擲錯誤,因此通常不會達到安全整數閾值,除非在非陣列物件上呼叫該方法。
Array.prototype.flat.call({}); // []
一些陣列方法設定陣列物件的 length 屬性。它們總是在標準化後設置值,因此 length 總是以整數結束。
const a = { length: 0.7 };
Array.prototype.push.call(a);
console.log(a.length); // 0
類陣列物件
術語類陣列物件指的是在上面描述的 length 轉換過程中不會丟擲錯誤的任何物件。實際上,此類物件應實際具有 length 屬性,並且在 0 到 length - 1 範圍內具有索引元素。(如果它沒有所有索引,它將在功能上等同於稀疏陣列。)當陣列方法作用於類陣列物件時,任何小於零或大於 length - 1 的整數索引都將被忽略。
許多 DOM 物件都是類陣列的——例如,NodeList 和 HTMLCollection。arguments 物件也是類陣列的。您可以對它們呼叫陣列方法,即使它們本身沒有這些方法。
function f() {
console.log(Array.prototype.join.call(arguments, "+"));
}
f("a", "b"); // 'a+b'
建構函式
Array()-
建立一個新的
Array物件。
靜態屬性
Array[Symbol.species]-
返回
Array建構函式。
靜態方法
Array.from()-
從可迭代物件或類陣列物件建立一個新的
Array例項。 Array.fromAsync()-
從非同步可迭代物件、可迭代物件或類陣列物件建立一個新的
Array例項。 Array.isArray()-
如果引數是陣列,則返回
true,否則返回false。 Array.of()-
根據引數的數量或型別,使用可變數量的引數建立一個新的
Array例項。
例項屬性
這些屬性在 Array.prototype 上定義,並由所有 Array 例項共享。
Array.prototype.constructor-
建立例項物件的建構函式。對於
Array例項,初始值是Array建構函式。 Array.prototype[Symbol.unscopables]-
包含在 ES2015 版本之前未包含在 ECMAScript 標準中,並且在
with語句繫結目的中被忽略的屬性名稱。
這些屬性是每個 Array 例項的自有屬性。
length-
反映陣列中元素的數量。
例項方法
Array.prototype.at()-
返回給定索引處的陣列項。接受負整數,從最後一項開始倒數。
Array.prototype.concat()-
返回一個新陣列,它是呼叫陣列與其它陣列和/或值連線的結果。
Array.prototype.copyWithin()-
在陣列內部複製一系列陣列元素。
Array.prototype.entries()-
返回一個包含陣列中每個索引的鍵/值對的新陣列迭代器物件。
Array.prototype.every()-
如果它在陣列中找到一個不滿足提供的測試函式的元素,則返回
false。否則,它返回true。 Array.prototype.fill()-
用靜態值填充陣列從起始索引到結束索引的所有元素。
Array.prototype.filter()-
返回一個新陣列,其中包含呼叫陣列中所有滿足提供的過濾函式返回
true的元素。 Array.prototype.find()-
返回陣列中滿足提供的測試函式的第一個元素的值,如果沒有找到合適的元素,則返回
undefined。 Array.prototype.findIndex()-
返回陣列中滿足提供的測試函式的第一個元素的索引,如果沒有找到合適的元素,則返回
-1。 Array.prototype.findLast()-
返回陣列中滿足提供的測試函式的最後一個元素的值,如果沒有找到合適的元素,則返回
undefined。 Array.prototype.findLastIndex()-
返回陣列中滿足提供的測試函式的最後一個元素的索引,如果沒有找到合適的元素,則返回
-1。 Array.prototype.flat()-
返回一個新陣列,其中所有子陣列元素都遞迴連線到指定的深度。
Array.prototype.flatMap()-
返回一個新陣列,透過對呼叫陣列的每個元素應用給定的回撥函式,然後將結果展平一層。
Array.prototype.forEach()-
對呼叫陣列中的每個元素呼叫一個函式。
Array.prototype.includes()-
確定呼叫陣列是否包含某個值,返回
true或false。 Array.prototype.indexOf()-
返回給定元素在呼叫陣列中首次出現(最小)的索引。
Array.prototype.join()-
將陣列的所有元素連線成一個字串。
Array.prototype.keys()-
返回一個包含呼叫陣列中每個索引的鍵的新陣列迭代器。
Array.prototype.lastIndexOf()-
返回給定元素在呼叫陣列中最後一次出現(最大)的索引,如果沒有找到,則返回
-1。 Array.prototype.map()-
返回一個新陣列,其中包含對呼叫陣列中的每個元素呼叫函式的結果。
Array.prototype.pop()-
從陣列中刪除最後一個元素並返回該元素。
Array.prototype.push()-
向陣列的末尾新增一個或多個元素,並返回陣列的新
length。 Array.prototype.reduce()-
對陣列的每個元素(從左到右)執行使用者提供的“reducer”回撥函式,將其歸結為單個值。
Array.prototype.reduceRight()-
對陣列的每個元素(從右到左)執行使用者提供的“reducer”回撥函式,將其歸結為單個值。
Array.prototype.reverse()-
就地反轉陣列元素的順序。(第一個變為最後一個,最後一個變為第一個。)
Array.prototype.shift()-
從陣列中刪除第一個元素並返回該元素。
Array.prototype.slice()-
提取呼叫陣列的一部分並返回一個新陣列。
Array.prototype.some()-
如果它在陣列中找到一個滿足提供的測試函式的元素,則返回
true。否則,它返回false。 Array.prototype.sort()-
就地排序陣列的元素並返回陣列。
Array.prototype.splice()-
從陣列中新增和/或刪除元素。
Array.prototype.toLocaleString()-
返回表示呼叫陣列及其元素的本地化字串。覆蓋
Object.prototype.toLocaleString()方法。 Array.prototype.toReversed()-
返回一個元素順序反轉的新陣列,而不修改原始陣列。
Array.prototype.toSorted()-
返回一個元素按升序排序的新陣列,而不修改原始陣列。
Array.prototype.toSpliced()-
返回一個新陣列,其中在給定索引處刪除和/或替換了一些元素,而不修改原始陣列。
Array.prototype.toString()-
返回表示呼叫陣列及其元素的字串。覆蓋
Object.prototype.toString()方法。 Array.prototype.unshift()-
向陣列的開頭新增一個或多個元素,並返回陣列的新
length。 Array.prototype.values()-
返回一個包含陣列中每個索引的值的新陣列迭代器物件。
Array.prototype.with()-
返回一個新陣列,其中給定索引處的元素替換為給定值,而不修改原始陣列。
Array.prototype[Symbol.iterator]()-
預設情況下是
values()方法的別名。
示例
本節提供了一些 JavaScript 中常見陣列操作的示例。
注意:如果您還不熟悉陣列基礎知識,請考慮首先閱讀JavaScript 初學者:陣列,其中解釋了陣列是什麼,幷包含了其他常見陣列操作的示例。
建立陣列
此示例展示了建立新陣列的三種方法:首先使用陣列字面量表示法,然後使用Array() 建構函式,最後使用String.prototype.split() 從字串構建陣列。
// 'fruits' array created using array literal notation.
const fruits = ["Apple", "Banana"];
console.log(fruits.length);
// 2
// 'fruits2' array created using the Array() constructor.
const fruits2 = new Array("Apple", "Banana");
console.log(fruits2.length);
// 2
// 'fruits3' array created using String.prototype.split().
const fruits3 = "Apple, Banana".split(", ");
console.log(fruits3.length);
// 2
從陣列建立字串
此示例使用join() 方法從 fruits 陣列建立字串。
const fruits = ["Apple", "Banana"];
const fruitsString = fruits.join(", ");
console.log(fruitsString);
// "Apple, Banana"
按索引訪問陣列項
此示例展示瞭如何透過指定陣列中項的位置索引號來訪問 fruits 陣列中的項。
const fruits = ["Apple", "Banana"];
// The index of an array's first element is always 0.
fruits[0]; // Apple
// The index of an array's second element is always 1.
fruits[1]; // Banana
// The index of an array's last element is always one
// less than the length of the array.
fruits[fruits.length - 1]; // Banana
// Using an index number larger than the array's length
// returns 'undefined'.
fruits[99]; // undefined
在陣列中查詢項的索引
此示例使用indexOf() 方法查詢字串 "Banana" 在 fruits 陣列中的位置(索引)。
const fruits = ["Apple", "Banana"];
console.log(fruits.indexOf("Banana"));
// 1
檢查陣列是否包含某個項
此示例展示了兩種檢查 fruits 陣列是否包含 "Banana" 和 "Cherry" 的方法:首先使用includes() 方法,然後使用indexOf() 方法測試非 -1 的索引值。
const fruits = ["Apple", "Banana"];
fruits.includes("Banana"); // true
fruits.includes("Cherry"); // false
// If indexOf() doesn't return -1, the array contains the given item.
fruits.indexOf("Banana") !== -1; // true
fruits.indexOf("Cherry") !== -1; // false
向陣列追加項
此示例使用push() 方法向 fruits 陣列追加一個新字串。
const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Orange");
console.log(fruits);
// ["Apple", "Banana", "Orange"]
console.log(newLength);
// 3
從陣列中刪除最後一項
此示例使用pop() 方法從 fruits 陣列中刪除最後一項。
const fruits = ["Apple", "Banana", "Orange"];
const removedItem = fruits.pop();
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItem);
// Orange
注意:pop() 只能用於刪除陣列中的最後一項。要從陣列末尾刪除多個項,請參見下一個示例。
從陣列末尾刪除多個項
此示例使用splice() 方法從 fruits 陣列中刪除最後 3 個項。
const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = -3;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]
將陣列截斷為僅包含其前 N 個項
此示例使用splice() 方法將 fruits 陣列截斷為僅包含其前 2 個項。
const fruits = ["Apple", "Banana", "Strawberry", "Mango", "Cherry"];
const start = 2;
const removedItems = fruits.splice(start);
console.log(fruits);
// ["Apple", "Banana"]
console.log(removedItems);
// ["Strawberry", "Mango", "Cherry"]
從陣列中刪除第一項
此示例使用shift() 方法從 fruits 陣列中刪除第一項。
const fruits = ["Apple", "Banana"];
const removedItem = fruits.shift();
console.log(fruits);
// ["Banana"]
console.log(removedItem);
// Apple
注意:shift() 只能用於刪除陣列中的第一項。要從陣列開頭刪除多個項,請參見下一個示例。
從陣列開頭刪除多個項
此示例使用splice() 方法從 fruits 陣列中刪除前 3 個項。
const fruits = ["Apple", "Strawberry", "Cherry", "Banana", "Mango"];
const start = 0;
const deleteCount = 3;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Banana", "Mango"]
console.log(removedItems);
// ["Apple", "Strawberry", "Cherry"]
向陣列新增一個新的第一項
此示例使用unshift() 方法在 fruits 陣列的索引 0 處新增一個新項——使其成為陣列中的新第一項。
const fruits = ["Banana", "Mango"];
const newLength = fruits.unshift("Strawberry");
console.log(fruits);
// ["Strawberry", "Banana", "Mango"]
console.log(newLength);
// 3
按索引刪除單個項
此示例使用splice() 方法從 fruits 陣列中刪除字串 "Banana"——透過指定 "Banana" 的索引位置。
const fruits = ["Strawberry", "Banana", "Mango"];
const start = fruits.indexOf("Banana");
const deleteCount = 1;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Strawberry", "Mango"]
console.log(removedItems);
// ["Banana"]
按索引刪除多個項
此示例使用splice() 方法從 fruits 陣列中刪除字串 "Banana" 和 "Strawberry"——透過指定 "Banana" 的索引位置,以及要刪除的總項數的計數。
const fruits = ["Apple", "Banana", "Strawberry", "Mango"];
const start = 1;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount);
console.log(fruits);
// ["Apple", "Mango"]
console.log(removedItems);
// ["Banana", "Strawberry"]
替換陣列中的多個項
此示例使用splice() 方法用新項替換 fruits 陣列中的最後 2 個項。
const fruits = ["Apple", "Banana", "Strawberry"];
const start = -2;
const deleteCount = 2;
const removedItems = fruits.splice(start, deleteCount, "Mango", "Cherry");
console.log(fruits);
// ["Apple", "Mango", "Cherry"]
console.log(removedItems);
// ["Banana", "Strawberry"]
遍歷陣列
此示例使用 for...of 迴圈遍歷 fruits 陣列,將每個項記錄到控制檯。
const fruits = ["Apple", "Mango", "Cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// Apple
// Mango
// Cherry
但是 for...of 只是遍歷任何陣列的眾多方法之一;有關更多方法,請參閱迴圈和迭代,並參閱 every()、filter()、flatMap()、map()、reduce() 和 reduceRight() 方法的文件——並參閱下一個示例,該示例使用forEach() 方法。
對陣列中的每個元素呼叫函式
此示例使用forEach() 方法對 fruits 陣列中的每個元素呼叫一個函式;該函式導致每個項以及該項的索引號被記錄到控制檯。
const fruits = ["Apple", "Mango", "Cherry"];
fruits.forEach((item, index, array) => {
console.log(item, index);
});
// Apple 0
// Mango 1
// Cherry 2
合併多個數組
此示例使用concat() 方法將 fruits 陣列與 moreFruits 數組合並,以生成一個新的 combinedFruits 陣列。請注意,fruits 和 moreFruits 保持不變。
const fruits = ["Apple", "Banana", "Strawberry"];
const moreFruits = ["Mango", "Cherry"];
const combinedFruits = fruits.concat(moreFruits);
console.log(combinedFruits);
// ["Apple", "Banana", "Strawberry", "Mango", "Cherry"]
// The 'fruits' array remains unchanged.
console.log(fruits);
// ["Apple", "Banana", "Strawberry"]
// The 'moreFruits' array also remains unchanged.
console.log(moreFruits);
// ["Mango", "Cherry"]
複製陣列
此示例展示了從現有 fruits 陣列建立新陣列的三種方法:首先使用展開語法,然後使用from() 方法,然後使用slice() 方法。
const fruits = ["Strawberry", "Mango"];
// Create a copy using spread syntax.
const fruitsCopy = [...fruits];
// ["Strawberry", "Mango"]
// Create a copy using the from() method.
const fruitsCopy2 = Array.from(fruits);
// ["Strawberry", "Mango"]
// Create a copy using the slice() method.
const fruitsCopy3 = fruits.slice();
// ["Strawberry", "Mango"]
所有內建的陣列複製操作(展開語法、Array.from()、Array.prototype.slice() 和 Array.prototype.concat())都會建立淺複製。如果您希望對陣列進行深複製,可以使用JSON.stringify() 將陣列轉換為 JSON 字串,然後使用JSON.parse() 將字串轉換回一個完全獨立於原始陣列的新陣列。
const fruitsDeepCopy = JSON.parse(JSON.stringify(fruits));
您還可以使用 structuredClone() 方法建立深複製,其優點是允許將源中的可轉移物件轉移到新副本中,而不僅僅是克隆。
最後,重要的是要理解將現有陣列分配給新變數並不會建立陣列或其元素的副本。相反,新變數只是原始陣列的引用或別名;也就是說,原始陣列的名稱和新變數的名稱只是同一個物件的兩個名稱(因此將始終被評估為嚴格相等)。因此,如果您對原始陣列的值或新變數的值進行任何更改,另一個也會改變:
const fruits = ["Strawberry", "Mango"];
const fruitsAlias = fruits;
// 'fruits' and 'fruitsAlias' are the same object, strictly equivalent.
fruits === fruitsAlias; // true
// Any changes to the 'fruits' array change 'fruitsAlias' too.
fruits.unshift("Apple", "Banana");
console.log(fruits);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
console.log(fruitsAlias);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
建立二維陣列
以下示例建立一個作為字串二維陣列的棋盤。第一步是透過將 board[6][4] 中的 'p' 複製到 board[4][4] 來完成的。[6][4] 處的舊位置被清空。
const board = [
["R", "N", "B", "Q", "K", "B", "N", "R"],
["P", "P", "P", "P", "P", "P", "P", "P"],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
[" ", " ", " ", " ", " ", " ", " ", " "],
["p", "p", "p", "p", "p", "p", "p", "p"],
["r", "n", "b", "q", "k", "b", "n", "r"],
];
console.log(`${board.join("\n")}\n\n`);
// Move King's Pawn forward 2
board[4][4] = board[6][4];
board[6][4] = " ";
console.log(board.join("\n"));
這是輸出:
R,N,B,Q,K,B,N,R P,P,P,P,P,P,P,P , , , , , , , , , , , , , , , , , , , , , , , , , , , , p,p,p,p,p,p,p,p r,n,b,q,k,b,n,r R,N,B,Q,K,B,N,R P,P,P,P,P,P,P,P , , , , , , , , , , , , , , , , , ,p, , , , , , , , , , p,p,p,p, ,p,p,p r,n,b,q,k,b,n,r
使用陣列製表一組值
const values = [];
for (let x = 0; x < 10; x++) {
values.push([2 ** x, 2 * x ** 2]);
}
console.table(values);
結果為:
// The first column is the index 0 1 0 1 2 2 2 4 8 3 8 18 4 16 32 5 32 50 6 64 72 7 128 98 8 256 128 9 512 162
使用匹配結果建立陣列
RegExp 與字串之間的匹配結果可以建立一個 JavaScript 陣列,該陣列具有提供匹配資訊的屬性和元素。此類陣列由 RegExp.prototype.exec() 和 String.prototype.match() 返回。
例如
// Match one d followed by one or more b's followed by one d
// Remember matched b's and the following d
// Ignore case
const myRe = /d(b+)(d)/i;
const execResult = myRe.exec("cdbBdbsbz");
console.log(execResult.input); // 'cdbBdbsbz'
console.log(execResult.index); // 1
console.log(execResult); // [ "dbBd", "bB", "d" ]
有關匹配結果的更多資訊,請參閱 RegExp.prototype.exec() 和 String.prototype.match() 頁面。
迭代方法中改變初始陣列
迭代方法不會改變呼叫它的陣列,但作為 callbackFn 提供的函式可以。要記住的關鍵原則是,只有 0 到 arrayLength - 1 之間的索引會被訪問,其中 arrayLength 是首次呼叫陣列方法時陣列的長度,但傳遞給回撥的元素是訪問索引時的值。因此:
callbackFn不會訪問在迭代方法呼叫開始時超出陣列初始長度的任何元素。- 對已訪問索引的更改不會導致
callbackFn再次在其上呼叫。 - 如果陣列中現有但尚未訪問的元素被
callbackFn更改,則傳遞給callbackFn的值將是該元素被訪問時的值。已刪除的元素不會被訪問。
警告:上述併發修改通常會導致難以理解的程式碼,通常應避免(特殊情況除外)。
以下示例使用 forEach 方法作為示例,但其他按升序訪問索引的方法也以相同的方式工作。我們首先定義一個輔助函式:
function testSideEffect(effect) {
const arr = ["e1", "e2", "e3", "e4"];
arr.forEach((elem, index, arr) => {
console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
effect(arr, index);
});
console.log(`Final array: [${arr.join(", ")}]`);
}
對尚未訪問的索引的修改將在索引到達時可見:
testSideEffect((arr, index) => {
if (index + 1 < arr.length) arr[index + 1] += "*";
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2*, e3, e4], index: 1, elem: e2*
// array: [e1, e2*, e3*, e4], index: 2, elem: e3*
// array: [e1, e2*, e3*, e4*], index: 3, elem: e4*
// Final array: [e1, e2*, e3*, e4*]
對已訪問索引的修改不會改變迭代行為,儘管之後陣列會有所不同:
testSideEffect((arr, index) => {
if (index > 0) arr[index - 1] += "*";
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1*, e2, e3, e4], index: 2, elem: e3
// array: [e1*, e2*, e3, e4], index: 3, elem: e4
// Final array: [e1*, e2*, e3*, e4]
在小於初始陣列長度的未訪問索引處插入 n 個元素將使其被訪問。原始陣列中現在索引大於初始陣列長度的最後 n 個元素將不會被訪問:
testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 0, "new");
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, new, e3, e4], index: 2, elem: new
// array: [e1, e2, new, e3, e4], index: 3, elem: e3
// Final array: [e1, e2, new, e3, e4]
// e4 is not visited because it now has index 4
在大於初始陣列長度的索引處插入 n 個元素不會使其被訪問:
testSideEffect((arr) => arr.push("new"));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4, new], index: 1, elem: e2
// array: [e1, e2, e3, e4, new, new], index: 2, elem: e3
// array: [e1, e2, e3, e4, new, new, new], index: 3, elem: e4
// Final array: [e1, e2, e3, e4, new, new, new, new]
在已訪問索引處插入 n 個元素不會使其被訪問,但它會將剩餘元素向後移動 n 個,因此當前索引及其之前的 n - 1 個元素將再次被訪問:
testSideEffect((arr, index) => arr.splice(index, 0, "new"));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [new, e1, e2, e3, e4], index: 1, elem: e1
// array: [new, new, e1, e2, e3, e4], index: 2, elem: e1
// array: [new, new, new, e1, e2, e3, e4], index: 3, elem: e1
// Final array: [new, new, new, new, e1, e2, e3, e4]
// e1 keeps getting visited because it keeps getting shifted back
刪除未訪問索引處的 n 個元素將使其不再被訪問。由於陣列已縮小,最後 n 次迭代將訪問越界索引。如果該方法忽略不存在的索引(參見陣列方法和空槽位),最後 n 次迭代將被跳過;否則,它們將收到 undefined:
testSideEffect((arr, index) => {
if (index === 1) arr.splice(2, 1);
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, e4], index: 2, elem: e4
// Final array: [e1, e2, e4]
// Does not visit index 3 because it's out-of-bounds
// Compare this with find(), which treats nonexistent indexes as undefined:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
if (index === 1) arr.splice(2, 1);
return false;
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e1, e2, e3, e4], index: 1, elem: e2
// array: [e1, e2, e4], index: 2, elem: e4
// array: [e1, e2, e4], index: 3, elem: undefined
刪除已訪問索引處的 n 個元素不會改變它們在被刪除之前已被訪問的事實。由於陣列已縮小,當前索引之後的下一個 n 個元素將被跳過。如果該方法忽略不存在的索引,最後 n 次迭代將被跳過;否則,它們將收到 undefined:
testSideEffect((arr, index) => arr.splice(index, 1));
// array: [e1, e2, e3, e4], index: 0, elem: e1
// Does not visit e2 because e2 now has index 0, which has already been visited
// array: [e2, e3, e4], index: 1, elem: e3
// Does not visit e4 because e4 now has index 1, which has already been visited
// Final array: [e2, e4]
// Index 2 is out-of-bounds, so it's not visited
// Compare this with find(), which treats nonexistent indexes as undefined:
const arr2 = ["e1", "e2", "e3", "e4"];
arr2.find((elem, index, arr) => {
console.log(`array: [${arr.join(", ")}], index: ${index}, elem: ${elem}`);
arr.splice(index, 1);
return false;
});
// array: [e1, e2, e3, e4], index: 0, elem: e1
// array: [e2, e3, e4], index: 1, elem: e3
// array: [e2, e4], index: 2, elem: undefined
// array: [e2, e4], index: 3, elem: undefined
對於按索引降序迭代的方法,插入會導致元素被跳過,刪除會導致元素被多次訪問。請自行調整上面的程式碼以檢視效果。
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-array-objects |
瀏覽器相容性
載入中…