第 18 章。陣列
目錄
購買書籍
(廣告,請勿封鎖。)

第 18 章。陣列

陣列是從索引 (自然數,從 0 開始) 到任意值的映射。值 (映射的範圍) 稱為陣列的元素。建立陣列最方便的方法是透過陣列文字。此類文字會列舉陣列元素;元素的位置會隱含指定其索引。

在本章中,我將首先介紹基本的陣列機制,例如索引存取和length 屬性,然後再介紹陣列方法。

概觀

本節提供陣列的快速概觀。詳細資訊將在後續說明。

作為第一個範例,我們透過陣列文字建立陣列 arr (請參閱建立陣列),並存取元素 (請參閱陣列索引)

> var arr = [ 'a', 'b', 'c' ]; // array literal
> arr[0]  // get element 0
'a'
> arr[0] = 'x';  // set element 0
> arr
[ 'x', 'b', 'c' ]

我們可以使用陣列屬性 length (請參閱length) 來移除和追加元素:

> var arr = [ 'a', 'b', 'c' ];
> arr.length
3
> arr.length = 2;  // remove an element
> arr
[ 'a', 'b' ]
> arr[arr.length] = 'd';  // append an element
> arr
[ 'a', 'b', 'd' ]

陣列方法 push() 提供另一種追加元素的方式

> var arr = [ 'a', 'b' ];
> arr.push('d')
3
> arr
[ 'a', 'b', 'd' ]

陣列是映射,不是元組

ECMAScript 標準將陣列指定為從索引到值的映射 (字典)。換句話說,陣列可能不是連續的,而且可能會有空洞。例如:

> var arr = [];
> arr[0] = 'a';
'a'
> arr[2] = 'b';
'b'
> arr
[ 'a', , 'b' ]

前述陣列有一個空洞:索引 1 沒有元素。 陣列中的空洞 會更詳細地說明空洞。

請注意,大多數 JavaScript 引擎會在內部最佳化沒有空洞的陣列,並將它們連續儲存。

陣列也可以有屬性

陣列仍然是物件,而且可以有物件屬性。這些屬性不被視為實際陣列的一部分;換句話說,它們不被視為陣列元素:

> var arr = [ 'a', 'b' ];
> arr.foo = 123;
> arr
[ 'a', 'b' ]
> arr.foo
123

建立陣列

您可以透過陣列文字建立陣列

var myArray = [ 'a', 'b', 'c' ];

陣列中的尾隨逗號會被忽略

> [ 'a', 'b' ].length
2
> [ 'a', 'b', ].length
2
> [ 'a', 'b', ,].length  // hole + trailing comma
3

Array 建構函式

兩種使用建構函式 Array 的方法:您可以建立具有指定長度的空陣列,或建立元素為指定值的陣列。對於此建構函式,new 是可選的:將其呼叫為一般函式 (不使用 new) 的效果與將其呼叫為建構函式相同。

建立具有指定長度的空陣列

具有指定長度的空陣列只會在其中有空洞!因此,使用此版本的建構函式的情況很少:

> var arr = new Array(2);
> arr.length
2
> arr  // two holes plus trailing comma (ignored!)
[ , ,]

某些引擎可能會在您以這種方式呼叫 Array() 時預先配置連續記憶體,這可能會稍微提升效能。不過,請務必確認增加的詳細程度和冗餘是否值得!

初始化包含元素的陣列(避免!)

這種呼叫 Array 的方式 類似陣列文字:

// The same as ['a', 'b', 'c']:
var arr1 = new Array('a', 'b', 'c');

問題是您無法建立只包含一個數字的陣列,因為這會被解譯為建立一個 length 為該數字的陣列

> new Array(2)  // alas, not [ 2 ]
[ , ,]

> new Array(5.7)  // alas, not [ 5.7 ]
RangeError: Invalid array length

> new Array('abc')  // ok
[ 'abc' ]

多維陣列

如果您需要多維的元素,您必須巢狀陣列。當您建立此類巢狀陣列時,最內層的陣列可以視需要而擴充。但是,如果您想要直接存取元素,您至少需要建立外層陣列。在以下範例中,我建立一個三乘三的井字遊戲矩陣。矩陣完全填滿資料(與讓列視需要而擴充相反):

// Create the Tic-tac-toe board
var rows = [];
for (var rowCount=0; rowCount < 3; rowCount++) {
    rows[rowCount] = [];
    for (var colCount=0; colCount < 3; colCount++) {
        rows[rowCount][colCount] = '.';
    }
}

// Set an X in the upper right corner
rows[0][2] = 'X';  // [row][column]

// Print the board
rows.forEach(function (row) {
    console.log(row.join(' '));
});

以下是輸出

. . X
. . .
. . .

我希望範例示範一般情況。很明顯地,如果矩陣很小而且具有固定維度,您可以透過陣列文字設定它

var rows = [ ['.','.','.'], ['.','.','.'], ['.','.','.'] ];

陣列索引

當您使用陣列索引時,您必須記住下列限制:

  • 索引是範圍在 0 ≤ i < 232−1 的數字 i
  • 最大長度為 232−1。

超出範圍的索引會被視為一般屬性金鑰(字串!)。它們不會顯示為陣列元素,而且不會影響屬性 length。例如

> var arr = [];

> arr[-1] = 'a';
> arr
[]
> arr['-1']
'a'

> arr[4294967296] = 'b';
> arr
[]
> arr['4294967296']
'b'

in 運算子與索引

in 運算子 會偵測物件是否具有具有特定金鑰的屬性。但是,它也可以用來判斷陣列中是否存在特定元素索引。例如:

> var arr = [ 'a', , 'b' ];
> 0 in arr
true
> 1 in arr
false
> 10 in arr
false

刪除陣列元素

除了刪除屬性之外, delete 運算子 也會刪除陣列元素。刪除元素會產生空洞length 屬性不會更新):

> var arr = [ 'a', 'b' ];
> arr.length
2
> delete arr[1]  // does not update length
true
> arr
[ 'a',  ]
> arr.length
2

您也可以透過減少陣列長度來刪除尾端的陣列元素(有關詳細資料,請參閱 length)。若要移除元素而不產生空洞(也就是後續元素的索引會遞減),請使用 Array.prototype.splice()(請參閱 新增和移除元素(破壞性))。在此範例中,我們在索引 1 移除兩個元素

> var arr = ['a', 'b', 'c', 'd'];
> arr.splice(1, 2) // returns what has been removed
[ 'b', 'c' ]
> arr
[ 'a', 'd' ]

陣列索引詳細資料

提示

這是進階章節。您通常不需要了解這裡說明的詳細資訊。

陣列索引並非表面上所見。到目前為止,我假裝陣列 索引是數字。而這是 JavaScript 引擎在內部實作陣列的方式。然而,ECMAScript 規範以不同的方式看待索引。改寫 第 15.4 節

  • 屬性金鑰 P(字串)是 陣列索引,當且僅當 ToString(ToUint32(P)) 等於 PToUint32(P) 不等於 232−1。這表示什麼意思,將在稍後說明。
  • 金鑰為陣列索引的陣列屬性稱為 元素

換句話說,在規範的世界中,括號中的所有值都會轉換為字串並解釋為屬性金鑰,甚至是數字。下列互動展示了這一點

> var arr = ['a', 'b'];
> arr['0']
'a'
> arr[0]
'a'

要成為陣列索引,屬性金鑰 P(字串!)必須等於下列運算的結果

  1. P 轉換為數字。
  2. 將數字轉換為 32 位元無符號整數。
  3. 將整數轉換為字串。

這表示陣列索引必須是 32 位元範圍 0 ≤ i < 232−1 中的字串化整數 i。規範中明確排除了上限(如前所述)。它保留給最大長度。讓我們使用 函式 ToUint32()透過位元運算子取得 32 位元整數 來了解此定義如何運作。

首先,不包含數字的字串總是轉換為 0,字串化後,不等於字串

> ToUint32('xyz')
0
> ToUint32('?@#!')
0

其次,超出範圍的字串化整數也會轉換為完全不同的整數,字串化後,不等於字串

> ToUint32('-1')
4294967295
> Math.pow(2, 32)
4294967296
> ToUint32('4294967296')
0

第三,字串化的非整數數字會轉換為整數,再次地,不同

> ToUint32('1.371')
1

請注意,規範也強制執行陣列索引沒有指數

> ToUint32('1e3')
1000

且沒有前導零

> var arr = ['a', 'b'];
> arr['0']  // array index
'a'
> arr['00'] // normal property
undefined

長度

length 屬性的基本功能是追蹤陣列中最高的索引:

> [ 'a', 'b' ].length
2
> [ 'a', , 'b' ].length
3

因此,length 沒有計算元素的數量,所以你必須自己撰寫函式來執行這項工作。例如

function countElements(arr) {
    var elemCount = 0;
    arr.forEach(function () {
        elemCount++;
    });
    return elemCount;
}

若要計算元素(非空洞),我們使用 forEach 會略過空洞的事實。以下是互動

> countElements([ 'a', 'b' ])
2
> countElements([ 'a', , 'b' ])
2

手動增加陣列長度

手動增加陣列長度對陣列的影響非常小;它只會建立空洞:

> var arr = [ 'a', 'b' ];
> arr.length = 3;
> arr  // one hole at the end
[ 'a', 'b', ,]

最後的結果在結尾有兩個逗號,因為尾隨逗號是可選的,因此總是會被忽略。

我們剛剛執行的動作沒有新增任何元素

> countElements(arr)
2

然而,length 屬性會作為指標,顯示插入新元素的位置。例如

> arr.push('c')
4
> arr
[ 'a', 'b', , 'c' ]

因此,透過 Array 建構函式設定陣列的初始長度,會建立一個完全空的陣列

> var arr = new Array(2);
> arr.length
2
> countElements(arr)
0

減少陣列長度

如果你減少陣列長度,所有在新長度及以上位置的元素都會被刪除:

> var arr = [ 'a', 'b', 'c' ];
> 1 in arr
true
> arr[1]
'b'

> arr.length = 1;
> arr
[ 'a' ]
> 1 in arr
false
> arr[1]
undefined

清除陣列

如果你將陣列長度設定為 0,它就會變成空的。這允許你為其他人清除陣列。例如:

function clearArray(arr) {
    arr.length = 0;
}

以下是互動

> var arr = [ 'a', 'b', 'c' ];
> clearArray(arr)
> arr
[]

不過,請注意,這種方法可能會很慢,因為每個陣列元素都會被明確刪除。諷刺的是,建立一個新的空陣列通常會更快

arr = [];

清除共用陣列

你必須了解將陣列長度設定為零會影響所有共用陣列的人員:

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1.length = 0;

> a1
[]
> a2
[]

相反地,指定一個空陣列不會

> var a1 = [1, 2, 3];
> var a2 = a1;
> a1 = [];

> a1
[]
> a2
[ 1, 2, 3 ]

最大長度

陣列的最大長度為 232−1:

> var arr1 = new Array(Math.pow(2, 32));  // not ok
RangeError: Invalid array length

> var arr2 = new Array(Math.pow(2, 32)-1);  // ok
> arr2.push('x');
RangeError: Invalid array length

陣列中的空洞

陣列是從索引到值的對應。這表示陣列可以有空洞,即小於長度且在陣列中遺失的索引。在其中一個索引讀取元素會傳回 undefined

提示

建議你在陣列中避免空洞。JavaScript 對它們的處理不一致(例如,有些方法會忽略它們,有些則不會)。謝天謝地,你通常不需要知道如何處理空洞:它們很少有用,而且會對效能產生負面影響。

建立空洞

你可以透過指定陣列索引來建立空洞:

> var arr = [];
> arr[0] = 'a';
> arr[2] = 'c';
> 1 in arr  // hole at index 1
false

您也可以透過在陣列字面中省略值來建立洞

> var arr = ['a',,'c'];
> 1 in arr  // hole at index 1
false

警告

您需要兩個尾隨逗號才能建立一個尾隨洞,因為最後一個逗號總是會被忽略

> [ 'a', ].length
1
> [ 'a', ,].length
2

稀疏陣列與稠密陣列

本節探討洞與元素中的 undefined 之間的差異。由於讀取洞會傳回 undefined,因此兩者非常相似。

具有洞的陣列稱為 稀疏沒有洞的陣列稱為 稠密。稠密陣列是連續的,且每個索引中都有元素,從 0 開始,以 length − 1 結束。讓我們比較以下兩個陣列,一個稀疏陣列和一個稠密陣列。兩者非常相似:

var sparse = [ , , 'c' ];
var dense  = [ undefined, undefined, 'c' ];

洞幾乎就像在同一個索引中具有元素 undefined。兩個陣列的長度相同

> sparse.length
3
> dense.length
3

但稀疏陣列在索引 0 處沒有元素

> 0 in sparse
false
> 0 in dense
true

透過 for 進行反覆運算對兩個陣列來說是 相同的:

> for (var i=0; i<sparse.length; i++) console.log(sparse[i]);
undefined
undefined
c
> for (var i=0; i<dense.length; i++) console.log(dense[i]);
undefined
undefined
c

透過 forEach 進行反覆運算會略過洞,但不會略過未定義的元素:

> sparse.forEach(function (x) { console.log(x) });
c
> dense.forEach(function (x) { console.log(x) });
undefined
undefined
c

哪些運算會忽略洞,哪些會考慮洞?

涉及 陣列的一些運算會忽略洞,而另一些運算則會考慮洞。本節說明詳細資訊。

陣列反覆運算方法

forEach() 會略過 洞:

> ['a',, 'b'].forEach(function (x,i) { console.log(i+'.'+x) })
0.a
2.b

every() 也會 略過洞(類似地:some()):

> ['a',, 'b'].every(function (x) { return typeof x === 'string' })
true

map() 會略過,但會保留洞

> ['a',, 'b'].map(function (x,i) { return i+'.'+x })
[ '0.a', , '2.b' ]

filter() 會消除洞:

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

其他陣列方法

join() 會將洞、undefinednull 轉換為空字串:

> ['a',, 'b'].join('-')
'a--b'
> [ 'a', undefined, 'b' ].join('-')
'a--b'

sort() 在排序時會保留洞

> ['a',, 'b'].sort()  // length of result is 3
[ 'a', 'b', ,  ]

for-in 迴圈

for-in 迴圈會正確列出屬性鍵(這是陣列索引的超集):

> for (var key in ['a',, 'b']) { console.log(key) }
0
2

Function.prototype.apply()

apply() 會將每個洞轉換成一個參數,其值為 undefined。以下互動示範了這一點:函式 f() 會將其參數傳回為陣列。當我們傳遞一個包含三個洞的陣列給 apply() 以呼叫 f() 時,後者會收到三個 undefined 參數

> function f() { return [].slice.call(arguments) }
> f.apply(null, [ , , ,])
[ undefined, undefined, undefined ]

這表示我們可以使用 apply() 來建立一個包含 undefined 的陣列:

> Array.apply(null, Array(3))
[ undefined, undefined, undefined ]

警告

apply() 將空陣列中的洞轉換為 undefined,但無法用於填補任意陣列(可能包含或不包含洞)中的洞。例如,任意陣列 [2]

> Array.apply(null, [2])
[ , ,]

陣列不包含任何洞,因此 apply() 應傳回相同的陣列。相反地,它傳回一個長度為 2 的空陣列(僅包含兩個洞)。這是因為 Array() 將單一數字解譯為陣列長度,而不是陣列元素。

從陣列中移除洞

正如我們所見,filter() 會移除洞:

> ['a',, 'b'].filter(function (x) { return true })
[ 'a', 'b' ]

使用自訂函式將任意陣列中的洞轉換為 undefined

function convertHolesToUndefineds(arr) {
    var result = [];
    for (var i=0; i < arr.length; i++) {
        result[i] = arr[i];
    }
    return result;
}

使用函式

> convertHolesToUndefineds(['a',, 'b'])
[ 'a', undefined, 'b' ]

新增和移除元素(破壞性)

節中的所有方法都是破壞性的:

Array.prototype.shift()

移除 索引 0 處的元素並傳回它。後續元素的索引會遞減 1:

> var arr = [ 'a', 'b' ];
> arr.shift()
'a'
> arr
[ 'b' ]
Array.prototype.unshift(elem1?, elem2?, ...)

給定的元素加到陣列的最前面。它會傳回新的長度:

> var arr = [ 'c', 'd' ];
> arr.unshift('a', 'b')
4
> arr
[ 'a', 'b', 'c', 'd' ]
Array.prototype.pop()

移除陣列的最後一個元素並傳回:

> var arr = [ 'a', 'b' ];
> arr.pop()
'b'
> arr
[ 'a' ]
Array.prototype.push(elem1?, elem2?, ...)

將指定的元素加入陣列的尾端。傳回新的長度:

> var arr = [ 'a', 'b' ];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]

apply()(請參閱 Function.prototype.apply(thisValue, argArray))讓您可以破壞性地將陣列 arr2 附加至另一個陣列 arr1

> var arr1 = [ 'a', 'b' ];
> var arr2 = [ 'c', 'd' ];

> Array.prototype.push.apply(arr1, arr2)
4
> arr1
[ 'a', 'b', 'c', 'd' ]
Array.prototype.splice(start, deleteCount?, elem1?, elem2?, ...)

start 開始,移除 deleteCount 個元素並插入指定的元素。換句話說,您會用 elem1elem2 等取代位置 startdeleteCount 個元素。此方法會傳回已移除的元素:

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(1, 2, 'X');
[ 'b', 'c' ]
> arr
[ 'a', 'X', 'd' ]

特殊參數值

  • start 可以為負數,如果是這樣,會將其加到長度以決定開始索引。因此,-1 指的是最後一個元素,依此類推。
  • deleteCount 為選用參數。如果省略(以及所有後續參數),則會移除索引 start 及其後的所有元素。

在此範例中,我們會移除倒數第二個索引及其後的所有元素

> var arr = [ 'a', 'b', 'c', 'd' ];
> arr.splice(-2)
[ 'c', 'd' ]
> arr
[ 'a', 'b' ]

排序和反轉元素(破壞性)

這些方法也是破壞性的

Array.prototype.reverse()

反轉陣列中元素的順序,並傳回對原始(已修改)陣列的參考:

> var arr = [ 'a', 'b', 'c' ];
> arr.reverse()
[ 'c', 'b', 'a' ]
> arr // reversing happened in place
[ 'c', 'b', 'a' ]
Array.prototype.sort(compareFunction?)

排序陣列並傳回:

> var arr = ['banana', 'apple', 'pear', 'orange'];
> arr.sort()
[ 'apple', 'banana', 'orange', 'pear' ]
> arr  // sorting happened in place
[ 'apple', 'banana', 'orange', 'pear' ]

請記住,排序會將值轉換為字串後再進行比較,這表示數字並非以數字方式排序

> [-1, -20, 7, 50].sort()
[ -1, -20, 50, 7 ]

您可以透過提供選用參數 compareFunction 來修正這個問題,此參數會控制排序方式。其簽章如下

function compareFunction(a, b)

此函式會比較 ab,並傳回

  • 小於零的整數(例如 -1),如果 a 小於 b
  • 零,如果 a 等於 b
  • 大於零的整數(例如 1),如果 a 大於 b

比較數字

對於數字,您可以直接傳回 a-b,但這可能會導致數字溢位。為了防止這種情況發生,您需要更冗長的程式碼:

function compareCanonically(a, b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

我不喜歡巢狀條件運算子。但在這種情況下,程式碼簡潔許多,我忍不住推薦它:

function compareCanonically(a, b) {
    return a < b ? -1 : (a > b ? 1 : 0);
}

使用函式

> [-1, -20, 7, 50].sort(compareCanonically)
[ -20, -1, 7, 50 ]

比較字串

對於字串,您可以使用 String.prototype.localeCompare(請參閱比較字串

> ['c', 'a', 'b'].sort(function (a,b) { return a.localeCompare(b) })
[ 'a', 'b', 'c' ]

比較物件

參數 compareFunction 也可用於排序物件:

var arr = [
    { name: 'Tarzan' },
    { name: 'Cheeta' },
    { name: 'Jane' } ];

function compareNames(a,b) {
    return a.name.localeCompare(b.name);
}

使用 compareNames 作為比較函式,arr 會依據 name 進行排序

> arr.sort(compareNames)
[ { name: 'Cheeta' },
  { name: 'Jane' },
  { name: 'Tarzan' } ]

串接、切片、合併(非破壞性)

下列方法對陣列執行各種非破壞性操作:

Array.prototype.concat(arr1?, arr2?, ...)

建立一個新陣列,其中包含接收者陣列的所有元素,接著是陣列 arr1 的所有元素,依此類推。如果其中一個參數不是陣列,則會將其作為元素新增到結果中(例如,這裡的第一個參數 'c'):

> var arr = [ 'a', 'b' ];
> arr.concat('c', ['d', 'e'])
[ 'a', 'b', 'c', 'd', 'e' ]

呼叫 concat() 的陣列不會改變

> arr
[ 'a', 'b' ]
Array.prototype.slice(begin?, end?)

將陣列元素複製到一個新陣列中,從 begin 開始,直到(不包含)end 處的元素:

> [ 'a', 'b', 'c', 'd' ].slice(1, 3)
[ 'b', 'c' ]

如果沒有 end,則會使用陣列長度

> [ 'a', 'b', 'c', 'd' ].slice(1)
[ 'b', 'c', 'd' ]

如果兩個索引都遺失,則會複製陣列

> [ 'a', 'b', 'c', 'd' ].slice()
[ 'a', 'b', 'c', 'd' ]

如果任一索引為負數,則會將陣列長度新增到該索引。因此,-1 指的是最後一個元素,依此類推

> [ 'a', 'b', 'c', 'd' ].slice(1, -1)
[ 'b', 'c' ]
> [ 'a', 'b', 'c', 'd' ].slice(-2)
[ 'c', 'd' ]
Array.prototype.join(separator?)

透過對所有陣列元素套用 toString(),並將字串置於結果之間的 separator 中,來建立一個字串。如果省略 separator,則會使用 ','

> [3, 4, 5].join('-')
'3-4-5'
> [3, 4, 5].join()
'3,4,5'
> [3, 4, 5].join('')
'345'

join() 會將 undefinednull 轉換為空字串

> [undefined, null].join('#')
'#'

陣列中的空洞也會轉換為空字串

> ['a',, 'b'].join('-')
'a--b'

搜尋值(非破壞性)

下列方法用於在陣列中搜尋值:

Array.prototype.indexOf(searchValue, startIndex?)

startIndex開始,在陣列中搜尋searchValue。它會傳回第一次出現的索引,如果沒有找到則傳回–1。如果startIndex為負數,則會加上陣列長度;如果沒有指定,則會搜尋整個陣列:

> [ 3, 1, 17, 1, 4 ].indexOf(1)
1
> [ 3, 1, 17, 1, 4 ].indexOf(1, 2)
3

搜尋時會使用嚴格相等(請參閱相等運算子:=== 與 ==),這表示indexOf()無法找到NaN

> [NaN].indexOf(NaN)
-1
Array.prototype.lastIndexOf(searchElement, startIndex?)

startIndex開始,向後在陣列中搜尋searchElement。它會傳回第一次出現的索引,如果沒有找到則傳回–1。如果startIndex為負數,則會加上陣列長度;如果沒有指定,則會搜尋整個陣列。搜尋時會使用嚴格相等(請參閱相等運算子:=== 與 ==

> [ 3, 1, 17, 1, 4 ].lastIndexOf(1)
3
> [ 3, 1, 17, 1, 4 ].lastIndexOf(1, -3)
1

反覆運算(非破壞性)

反覆運算方法使用函式來反覆運算陣列。我將反覆運算方法分為三種類型,所有類型都是非破壞性的:檢查方法主要用於觀察陣列的內容;轉換方法從接收者衍生出一個新陣列;簡化方法根據接收者的元素計算結果。

檢查方法

本節中描述的每種方法如下所示:

arr.examinationMethod(callback, thisValue?)

此類方法會採用下列參數

  • callback是第一個參數,它會呼叫的函式。根據檢查方法的不同,callback 會傳回布林值或不傳回任何值。其簽章如下

    function callback(element, index, array)

    element是陣列元素,供callback處理,index是元素的索引,而array則是examinationMethod被呼叫的陣列。

  • thisValue可讓您設定callback內部this的值。

以下是簽章剛才描述的檢查方法:

Array.prototype.forEach(callback, thisValue?)

反覆運算陣列中的元素

var arr = [ 'apple', 'pear', 'orange' ];
arr.forEach(function (elem) {
    console.log(elem);
});
Array.prototype.every(callback, thisValue?)

如果 callback 對每個元素都傳回 true,則傳回 true。只要 callback 傳回 false,就會停止反覆運算。請注意,如果不傳回值,會隱含傳回 undefined,而 every() 會將其解譯為 falseevery() 的運作方式類似於全稱量詞(「對於所有」)。

這個範例會檢查陣列中的每個數字是否都是偶數

> function isEven(x) { return x % 2 === 0 }
> [ 2, 4, 6 ].every(isEven)
true
> [ 2, 3, 4 ].every(isEven)
false

如果陣列是空的,結果會是 true(而且不會呼叫 callback

> [].every(function () { throw new Error() })
true
Array.prototype.some(callback, thisValue?)

如果 callback 對至少一個元素傳回 true,則傳回 true。只要 callback 傳回 true,就會停止反覆運算。請注意,如果不傳回值,會隱含傳回 undefined,而 some 會將其解譯為 falsesome() 的運作方式類似於存在量詞(「存在」)。

這個範例會檢查陣列中是否有偶數

> function isEven(x) { return x % 2 === 0 }
> [ 1, 3, 5 ].some(isEven)
false
> [ 1, 2, 3 ].some(isEven)
true

如果陣列是空的,結果會是 false(而且不會呼叫 callback

> [].some(function () { throw new Error() })
false

forEach() 的一個潛在陷阱是,它不支援 break 或類似用於提早中止迴圈的語法。如果你需要這麼做,可以使用 some()

function breakAtEmptyString(strArr) {
    strArr.some(function (elem) {
        if (elem.length === 0) {
            return true; // break
        }
        console.log(elem);
        // implicit: return undefined (interpreted as false)
    });
}

some() 會在發生中斷時傳回 true,否則傳回 false。這能讓你根據反覆運算是否成功完成而做出不同的反應(這在使用 for 迴圈時會有點棘手)。

轉換方法

轉換 方法會取得輸入陣列並產生輸出陣列,而 callback 則控制輸出的產生方式。callback 的簽章與檢查相同:

function callback(element, index, array)

有兩種轉換方法:

Array.prototype.map(callback, thisValue?)

每個輸出陣列元素都是將 callback 套用至輸入元素的結果。例如

> [ 1, 2, 3 ].map(function (x) { return 2 * x })
[ 2, 4, 6 ]
Array.prototype.filter(callback, thisValue?)

輸出陣列 僅包含那些輸入元素,其中 callback 傳回 true。例如:

> [ 1, 0, 3, 0 ].filter(function (x) { return x !== 0 })
[ 1, 3 ]

簡化方法

對於簡化,callback 具有不同的簽章:

function callback(previousValue, currentElement, currentIndex, array)

參數 previousValue 是 callback 先前傳回的值。當 callback 第一次呼叫時,有兩種可能性(說明是針對 Array.prototype.reduce();括號中提到與 reduceRight() 的差異)

  • 已提供明確的 initialValue。然後 previousValueinitialValue,而 currentElement 是第一個陣列元素(reduceRight:最後一個陣列元素)。
  • 未提供明確的 initialValue。然後 previousValue 是第一個陣列元素,而 currentElement 是第二個陣列元素(reduceRight:最後一個陣列元素和倒數第二個陣列元素)。

有兩種簡化方法

Array.prototype.reduce(callback, initialValue?)

從左到右反覆運算,並呼叫之前草擬的 callback。此方法的結果是 callback 傳回的最後一個值。此範例計算所有陣列元素的總和:

function add(prev, cur) {
    return prev + cur;
}
console.log([10, 3, -1].reduce(add)); // 12

如果您對單一元素的陣列呼叫 reduce,則會傳回該元素

> [7].reduce(add)
7

如果您對空陣列呼叫 reduce,則必須指定 initialValue,否則會產生例外狀況

> [].reduce(add)
TypeError: Reduce of empty array with no initial value
> [].reduce(add, 123)
123
Array.prototype.reduceRight(callback, initialValue?)
運作方式與 reduce() 相同,但從右到左反覆運算。

注意

在許多函式程式語言中,reduce 稱為 foldfoldl(左折疊),而 reduceRight 稱為 foldr(右折疊)。

檢視 reduce 方法的另一種方式是,它實作 n 元運算子 OP

OP1≤i≤n xi

透過連續應用二元運算子 op2

(...(x1 op2 x2) op2 ...) op2 xn

這正是前一個程式碼範例所做的:我們透過 JavaScript 的二元加法運算子實作陣列的 n 元加法運算子。

讓我們透過以下函式來檢視兩個反覆運算方向,作為範例

function printArgs(prev, cur, i) {
    console.log('prev:'+prev+', cur:'+cur+', i:'+i);
    return prev + cur;
}

正如預期,reduce() 從左到右反覆運算

> ['a', 'b', 'c'].reduce(printArgs)
prev:a, cur:b, i:1
prev:ab, cur:c, i:2
'abc'
> ['a', 'b', 'c'].reduce(printArgs, 'x')
prev:x, cur:a, i:0
prev:xa, cur:b, i:1
prev:xab, cur:c, i:2
'xabc'

reduceRight() 從右到左反覆運算

> ['a', 'b', 'c'].reduceRight(printArgs)
prev:c, cur:b, i:1
prev:cb, cur:a, i:0
'cba'
> ['a', 'b', 'c'].reduceRight(printArgs, 'x')
prev:x, cur:c, i:2
prev:xc, cur:b, i:1
prev:xcb, cur:a, i:0
'xcba'

最佳實務:反覆運算陣列

若要反覆運算陣列 arr,您有兩個選項:

  • 一個簡單的 for 迴圈(請參閱 for

    for (var i=0; i<arr.length; i++) {
        console.log(arr[i]);
    }
  • 其中一個陣列反覆運算方法(請參閱 反覆運算(非破壞性))。例如,forEach()

    arr.forEach(function (elem) {
        console.log(elem);
    });

不要使用 for-in 迴圈(請參閱 for-in)來反覆運算陣列。它會反覆運算索引,而非值。而且在這樣做的同時,它會包含一般屬性的金鑰,包括繼承的屬性。

下一篇:19. 正規表示式