把JavaScript標準庫之陣列一網打盡

吳少666發表於2018-01-04

學習任何程式語言,陣列都是繞不過去的坎,每個程式語言都在其標準庫裡面內建了功能強大的Array物件。通過參考阮一峰教程和MDN,我把陣列常見的方法以及一些誤區總結如下,內容較多,而且會繼續補充,希望這一篇文章可以把陣列的知識一網打盡。

1. 陣列的簡單概念

1.1 陣列是什麼呢?

程式設計總要和資料打交道,常見的普通的資料由基本資料型別可以定義,一些具有多重屬性、內容的資料就需要複雜的資料型別去定義,也就是物件來定義,陣列也是物件的一種。

  • 為了方便理解,我們可以認為陣列是具有一定順序的複雜資料的組合(與物件的無序區別),每個位置對應一個索引,索引從0開始,具有length屬性,而且length屬性是可變的

1.2 陣列如何定義

  • 第一種方法是通過Array建構函式來定義(該方法並不常用)
var arr1 = new Array(3)
undefined
arr1
(3) [empty × 3]
    length: 3
    __proto__: Array(0)
複製程式碼

以上是控制檯列印結果,構造了一個長度為3的、每個元素為空的陣列。


以上的寫法有個小bug 雖然元素為空,但是正常來說,索引應該存在的,但是事實是 索引竟然不存在

arr1[0]
undefined
arr1[1]
undefined
arr1[2]
undefined
0 in arr1
false
1 in arr1
false
2 in arr1
複製程式碼

索引0、1、2處是undefined,完全合理,但是索引不存在,很奇怪


而且new不寫也是一樣的結果。

var arr2 = Array(3)
undefined
arr2
(3) [empty × 3]
    length: 3
    __proto__: Array(0)
複製程式碼
  • 但是採用建構函式的這種方法容易產生一些歧義,不同的引數個數,會產生如下五種不同的結果。
1.2.1 建構函式不寫引數
var arr3 = new Array
undefined
arr3
[]
    length:0
    __proto__:Array(0
複製程式碼

此時構造出空的陣列,而且發現建構函式的()寫不寫都可以

1.2.2 建構函式寫1個正整數引數

那這個正整數引數就是構造出來的陣列的長度。

1.2.3 建構函式引數是一個非正整數(字串、boolean、物件等其他值)
var arr = new Array('jjj')
undefined
arr
["jjj"]
    0: "jjj"
    length: 1
    __proto__: Array(0)
複製程式碼
var arr = new Array(false)
undefined
arr
[false]
    0: false
    length: 1
    __proto__: Array(0)
複製程式碼
var arr = new Array({0: '我是一個物件'})
undefined
arr
[{…}]
    0: {0: "我是一個物件"}
    length: 1
    __proto__: Array(0)
複製程式碼

這個非正整數就是陣列的內容

1.2.4 建構函式寫多個引數
var arr4 = new Array(1, 2)
undefined
arr4
(2) [1, 2]
    0: 1
    1: 2
    length: 2
    __proto__: Array(0)
複製程式碼

此時直接構造出0索引是元素1、1索引是元素2的陣列物件。

var arr4 = new Array('aa', 'ff', 10, 0)
undefined
arr4
(4) ["aa", "ff", 10, 0]
    0: "aa"
    1: "ff"
    2: 10
    3: 0
    length: 4
    __proto__:Array(0)
複製程式碼

即多引數時,所有引數都是返回的新陣列的成員

1.2.5 建構函式引數是非正整數,報錯
new Array(-1)
VM376:1 Uncaught RangeError: Invalid array length
    at <anonymous>:1:1
(anonymous) @ VM376:1
new Array(3.2)
VM377:1 Uncaught RangeError: Invalid array length
    at <anonymous>:1:1
(anonymous) @ VM377:1
複製程式碼
1.2.6 陣列定義的正確方法

為了避免上述的各種奇怪理解,實際中直接用字面量定義陣列

var arr = ['這樣子', '定義', 'is', true, 1, {'good': '我是陣列索引為5的元素的值'}]
undefined
arr
(6) ["這樣子", "定義", "is", true, 1, {…}]
    0: "這樣子"
    1: "定義"
    2: "is"
    3: true
    4: 1
    5: {good: "我是陣列索引為5的元素的值"}
    length: 6
    __proto__:Array(0)

複製程式碼

2. 陣列的length屬性解疑

如果你是初學者,一定要記住陣列的length屬性和這個陣列的元素個數無關,愛幾個元素幾個元素,length並不是計數的作用。這是我自學時對陣列長度最大的誤解。 正確的理解是:陣列的length屬性等於最大正整數索引 + 1 而且陣列的索引可以改變,那麼length屬性也是一個動態的值,可以變化。

var arr = []
undefined
arr
[]
    length:0
    __proto__:Array(0)
arr[10] = '我是第10個元素,我前面沒有元素,但是陣列的長度絕對是11,你信不信'
"我是第10個元素,我前面沒有元素,但是陣列的長度絕對是11,你信不信"
arr
(11) [empty × 10, "我是第10個元素,我前面沒有元素,但是陣列的長度絕對是11,你信不信"]
    10:"我是第10個元素,我前面沒有元素,但是陣列的長度絕對是11,你信不信"
    length:11
    __proto__:Array(0)
複製程式碼

這個例子一開始是個空陣列,長度是0,直接給他一個索引10,可以發現長度立馬變為11。

arr[100] = '這次陣列長度絕對是101'
"這次陣列長度絕對是101"
arr.length
101
複製程式碼

通過以上的例子,我們反向推理,可以明白陣列長度根本不連續,是動態變化的,即陣列長度是可寫的。唯一的不變真理是,它的長度永遠等於最大正整數索引+1。

2.1 把陣列清空的方法

由以上知識可以知道陣列長度可以人為改變,進而大膽的猜想,改變長度會不會把陣列清空呢?

var arrDemo = ['this', 'is', 'test']
undefined
arrDemo
(3) ["this", "is", "test"]
    0: "this"
    1: "is"
    2: "test"
    length: 3
    __proto__: Array(0)
arrDemo['length'] = 2
2
arrDemo
(2) ["this", "is"]
    0: "this"
    1: "is"
    length: 2
    __proto__: Array(0)
arrDemo['length'] = 1
1
arrDemo
["this"]
    0: "this"
    length: 1
    __proto__: Array(0)
arrDemo['length'] = 0
0
arrDemo
[]
    length: 0
    __proto__: Array(0)
複製程式碼

把陣列length設為0,證明可以清空陣列。

2.2 有趣的一點

由於陣列本質上是物件的一種,所以我們可以為陣列新增屬性,但是這不影響length屬性的值。 一定不要有思維定式,以為新增幾個新元素,長度就會加幾個。

var arr = []
undefined
arr
[]
    length:0
    __proto__:Array(0)
arr['add'] = '我加一個新元素,長度絕對還是0'
"我加一個新元素,長度絕對還是0"
arr
[add: "我加一個新元素,長度絕對還是0"]
    add: "我加一個新元素,長度絕對還是0"
    length:0
    __proto__:Array(0)
arr['add1'] = '我又加一個新元素,長度絕對還是0'
"我又加一個新元素,長度絕對還是0"
arr
[add: "我加一個新元素,長度絕對還是0", add1: "我又加一個新元素,長度絕對還是0"]
    add: "我加一個新元素,長度絕對還是0"
    add1: "我又加一個新元素,長度絕對還是0"
    length: 0
    __proto__:Array(0)
複製程式碼

通過這個例子,一開始元素長度為0,只要你沒新增一個正整數的索引,無論你新增多少其他元素,長度永遠不會變化。

  • 注意:方括號運算子裡面一定要用引號,我總是手抖忘了加。

3. 偽陣列(array-like object)

如果一個物件的所有鍵名都是正整數或零,並且有length屬性,那麼這個物件就很像陣列,語法上稱為“類似陣列的物件”

var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3,
}
undefined
obj
{0: "a", 1: "b", 2: "c", length: 3}
    0: "a"
    1: "b"
    2: "c"
    length: 3
    __proto__: Object

obj[0]
"a"
obj[2]
"c"
複製程式碼

上面的物件,看著結構特別像陣列,但是絕對不是陣列。 因為__proto__指向的就不是Array的prototype,沒有指向Array的共有屬性,再怎麼像也只是模仿,本質不同。不具備陣列的其他方法(第四部分將要列舉的方法)。

3.1 陣列的本質

由偽陣列的問題引出真正的陣列應該具備什麼特點

陣列.png
__proto__必須指向陣列的公有屬性才是真正的陣列物件。

4. 陣列例項的常見簡單的方法(可以無參或者引數很簡單)

4.1 判斷陣列還是物件

var arr = ['a']
undefined
Array.isArray(arr)
true
var obj = {
	0: 'a',
	1: 'b',
	2: 'c',
	length: 3,
}
undefined
Array.isArray(obj)
false
複製程式碼

Array.isArray()方法可以判斷是不是陣列物件,以前學過的instanceOf也可以判斷。

arr instanceof Array
true
obj instanceof Array
false
複製程式碼

所以現在有兩個方法可以區分是陣列還是物件了。

4.2 valueOf(),toString()

  • valueOf()返回陣列本身
var arr = ['a', 'b']
undefined
arr.valueOf()
(2) ["a", "b"]
arr.toString()
"a,b"
複製程式碼
  • toString()返回陣列的字串形式

4.3 push()

var arr = ['a', 'b']
undefined
arr.push('f')
3
arr
(3) ["a", "b", "f"]
複製程式碼

向陣列的末尾新增元素,返回新增成功後的陣列的長度 會改變原陣列

4.4 pop()

arr.pop()
"f"
arr
(2) ["a", "b"]

複製程式碼

刪除陣列的最後一個元素,並返回刪除的這個元素。

[].pop() // undefined
複製程式碼
  • 注意:對空陣列使用pop方法,不會報錯,而是返回undefined。 這個方法會改變原陣列

push() 和pop()方法一起使用可以模擬棧的資料結構


4.5 join()

以某種形式把陣列的所有成員以字串的形式返回

arr
(2) ["a", "b"]
arr.join('-')
"a-b"
複製程式碼

以上是以中劃線的形式連線起來

arr.join()
"a,b"
複製程式碼

如果沒有規定格式,則以逗號分隔

var arr = ['a', 'rr', null, undefined]
undefined
arr
(4) ["a", "rr", null, undefined]
arr.join()
"a,rr,,"
複製程式碼
  • 注意:如果字串中有null和undefined的,會被轉成空字串。 該方法不會改變原陣列

4.6 concat()

是一個專業合併陣列的方法。

var arr = ['a', 'rr', null, undefined]
undefined
arr.concat(['rrr'])
(5) ["a", "rr", null, undefined, "rrr"]
arr
(4) ["a", "rr", null, undefined]
複製程式碼

把一個新陣列新增到舊陣列的後面,返回生成的新陣列。 不會改變原陣列

4.7 shift()

刪除陣列的第一個元素,並返回刪除的那個元素

arr
(4) ["a", "rr", null, undefined]
arr.shift()
"a"
arr
(3) ["rr", null, undefined]
複製程式碼

會改變原陣列


push()與shift()方法結合,可以模擬佇列的資料結構


4.8 unshift()

在陣列的第一個位置新增元素,並返回新增新元素後的陣列長度

arr
(3) ["rr", null, undefined]
arr.unshift('ffff')
4
arr
(4) ["ffff", "rr", null, undefined]
複製程式碼

和shift()方法的作用正好相反。 一定會改變原陣列

4.9 reverse()

反轉陣列,返回反轉後的陣列

arr
(4) ["ffff", "rr", null, undefined]
arr.reverse()
(4) [undefined, null, "rr", "ffff"]
arr
(4) [undefined, null, "rr", "ffff"]
複製程式碼

會改變原陣列

4.10 slice()

提取原陣列的一部分,返回一個新的陣列

arr
(4) [undefined, null, "rr", "ffff"]
arr.slice(1,3)
(2) [null, "rr"]
arr.slice()
(4) [undefined, null, "rr", "ffff"]
arr.slice(1)
(3) [null, "rr", "ffff"]
arr
(4) [undefined, null, "rr", "ffff"]
複製程式碼

arr.slice(1,3)從索引為1的位置開始擷取,到索引3停止,但是不包括索引3。 arr.slice()無參是原陣列的拷貝 arr.slice(1)從索引為1的位置開始擷取,到末尾。

var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]
複製程式碼

如果slice方法的引數是負數,則表示倒數計算的位置。 上面程式碼中,-2表示倒數計算的第二個位置,-1表示倒數計算的第一個位置。


slice()方法可以把偽陣列變成真的陣列


不會改變原陣列

4.11 splice()

刪除原陣列的一部分成員,返回被刪的元素。

arr
(4) [undefined, null, "rr", "ffff"]
arr.splice(1, 3)
(3) [null, "rr", "ffff"]
arr
[undefined]
複製程式碼

arr.splice(1,3),從索引1開始刪除,刪3個元素!!! 一定要注意和slice區分:splice的第一個引數是刪除的起始位置,第二個引數是被刪除的元素個數。如果後面還有更多的引數,則表示這些就是要被插入陣列的新元素。

var arr = ['1', 'aaa', 'ff', 'aff', 1]
undefined
arr.splice(1, 3, 'wu', 999)
(3) ["aaa", "ff", "aff"]
arr
(4) ["1", "wu", 999, 1]
複製程式碼

arr.splice(1, 3, 'wu', 999),從索引1開始刪了3個元素,然後加上兩個元素,'wu'和999。 負數引數同樣表示陣列倒數第幾個位置 會改變原陣列
splice()有兩個變式

  • 變式1:我只是想單純的插入一個元素
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
複製程式碼

把第二個引數設為0,就可以在第2個位置插入一個元素了

  • 變式2:我只給一個引數,就是拆分陣列,變為兩個新陣列
a
(5) [1, 1, 1111, 1, 10]
a.splice(2)
(3) [1111, 1, 10]
a
(2) [1, 1]
複製程式碼

a.splice(2)從第三個索引處拆分這個陣列。

4.12 indexOf(),lastIndexOf()

var arr = ['a', 'f', 'f', 1]
undefined
arr
(4) ["a", "f", "f", 1]
    0: "a"
    1: "f"
    2: "f"
    3: 1
    length: 4
    __proto__: Array(0)
arr.indexOf(1)
3
arr.indexOf('f', 3)
-1
arr.lastIndexOf('f')
2
複製程式碼

indexOf(),返回括號裡面 的元素第一次出現的位置。 如果有兩個引數則是表示搜尋的位置從第二個引數開始。 如果找不到該元素,則返回-1。 lastIndexOf()返回括號裡面的元素最後一次出現的位置。

  • 一個MDN的實戰例子:獲得陣列裡面某個元素出現的所有位置(利用迴圈和返回值-1的特點)
var arr = ['a', 0, 'a', 'b', 'a'];
var arrTemp = []; //空陣列用來儲存目標元素出現的所有索引
var element = 'a';
var index = arr.indexOf(element);
while(index != -1){
	arrTemp.push(index);
	index = arr.indexOf(element, index + 1);
}
console.log(arrTemp);
(3) [0, 2, 4] //'a'出現在0、2、4索引位置處
複製程式碼

注意:這裡有個例外 陣列裡麵包含NaN時無法判斷

var arr = ['a', 'f', 'f', NaN]
undefined
arr
(4) ["a", "f", "f", NaN]
    0: "a"
    1: "f"
    2: "f"
    3: NaN
    length: 4
    __proto__: Array(0)
arr.indexOf(NaN)
-1
arr.lastIndexOf('NaN')
-1
複製程式碼

arr陣列的第四個位置是NaN,但是無法獲得索引。 因為indexOf(),lastIndexOf()是嚴格按照===操作符來檢測的,而NaN是唯一的不與自身相等的值。

NaN === NaN
false
1 === 1
true
'a' === 'a'
true
複製程式碼

奇葩啊,NaN與自己都不相等


5. 陣列例項的常見覆雜的方法(引數是另一個函式)

5.1 sort()

下面MDN的解釋非常棒

sort() 方法在適當的位置對陣列的元素進行排序,並返回陣列。 sort 排序不一定是穩定的。預設排序順序是根據字串Unicode碼點。

var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); 
// ['apples', 'bananas', 'cherries']

var scores = [1, 10, 21, 2]; 
scores.sort(); 
// [1, 10, 2, 21]
// 注意10在2之前,
// 因為在 Unicode 指標順序中"10""2"之前

var things = ['word', 'Word', '1 Word', '2 Words'];
things.sort(); 
// ['1 Word', '2 Words', 'Word', 'word']
// 在Unicode中, 數字在大寫字母之前,
// 大寫字母在小寫字母之前.
複製程式碼

上述程式碼兩點注意

  • 第一點是 上述程式碼中的第二部分的[1, 10, 2, 21]是因為 10的Unicode編碼是\u0031\u0030,2的Unicode編碼是\u0032,所以10排在2的前面
  • 第二點是上述程式碼中的第三部分的['1 Word', '2 Words', 'Word', 'word']是因為
    'Word'的Unicode編碼是
\u0026\u0023\u0033\u0039\u003b\u0057\u006f\u0072\u0064\u0026\u0023\u0033\u0039\u003b
複製程式碼

'word'的Unicode編碼是

\u0026\u0023\u0033\u0039\u003b\u0077\u006f\u0072\u0064\u0026\u0023\u0033\u0039\u003b
複製程式碼

所以 'Word'排在'word'前面。 各種編碼查詢站長工具
sort方法明顯的會改變原陣列啊

  • 我們通常不想使用預設的升序排列,sort方法可以傳入函式來改變順序。 MDN的語法是arr.sort(compareFunction) compareFunction這個函式用來指定按某種順序進行排列的函式。如果省略,元素按照轉換為的字串的諸個字元的Unicode位點進行排序。 compareFunction這個函式基本的規則是傳入兩個引數
function compareNumbers(a, b) {
  return a - b;
}
複製程式碼
a,b引數比較 代表的意思
compareFunction(a, b) < 0 a在b之前
compareFunction(a, b) > 0 b在a之前
var a = [1, 20, 30, -7]
undefined
a
(4) [1, 20, 30, -7]
a.sort(function(a,b){return b-a})
(4) [30, 20, 1, -7]
複製程式碼

降序排列。

  • 也可以根據具體需求來根據屬性來排列
var students = ['小明','小紅','小花'];
 var scores = { 小明: 59, 小紅: 99, 小花: 80 }; 
students.sort(function(a, b){
    return scores[b] - scores[a]
});
(3) ["小紅", "小花", "小明"]
複製程式碼

以上是把三個學生根據成績從大到小排列的

5.2 map()

map() 方法建立一個新陣列,其結果是該陣列中的每個元素都呼叫一個提供的函式後返回的結果。 不影響原陣列。

var arr = ['aa', 'bb', 'cc']
arr.map(function(value){
	return value = value + "f"
})
(3) ["aaf", "bbf", "ccf"]
arr
(3) ["aa", "bb", "cc"]
複製程式碼

以上程式碼中map()方法裡面傳入的函式是一個把陣列每個值都加上一個'f'。 每個元素末尾都加上一個'f',然後返回這個新的陣列,原陣列沒有任何變化的。 我初學的時候,看到上述程式碼反正很懵逼,這玩意咋出來的這個結果呢。琢磨了很久,還是覺得MDN的解釋明白,只不過需要看個3、4遍就能明白了。 語法規範是:

let new_array = arr.map(function callback(currentValue, index, array) { 
    // Return element for new_array 
}[, thisArg])
複製程式碼

callback 生成新陣列元素的函式,使用三個引數: currentValue callback 的第一個引數,陣列中正在處理的當前元素。 index callback 的第二個引數,陣列中正在處理的當前元素的索引。 array callback 的第三個引數,map 方法被呼叫的陣列。 thisArg 可選的。執行 callback 函式時 使用的this 值。 返回值 一個新陣列,每個元素都是回撥函式的結果。

[1, 2, 3].map(function(currentValue, index, arr){
	return currentValue*index
})
(3) [0, 2, 6]
複製程式碼

其實callback 的第三個引數可以不寫,也知道呼叫的到底是哪個Array。

[1, 2, 3].map(function(currentValue, index){
	return currentValue*index
})
(3) [0, 2, 6]
複製程式碼

當你用map()方法的時候,callback 函式會被自動傳入三個引數:陣列的每一個元素,元素索引,原陣列本身。既然原陣列本身可以省略,那麼由剩下的兩個特點我們發散一下,會想到前面我們講過,偽陣列(比如字串)也具備這兩個特點會不會也能用map()方法呢,接下來做個實驗。
哈哈哈哈,愚蠢的人類,你想的美,怎麼可能直接使用呢,必須把偽陣列轉換一下的。

  • 第一種轉換方法
var upper = function (str){
	return str.toUpperCase();
};
[].map.call('abc', upper)
(3) ["A", "B", "C"]
複製程式碼

以上是通過map函式的call方法間接使用

  • 第二種轉換方法
'abc'.split('').map(upper)
(3) ["A", "B", "C"]
複製程式碼

'abc'.split('')把字串轉成陣列["a", "b", "c"]


至此,字串和陣列相互轉化的方法,都學到了,總結如下。

  • 陣列轉字串 三種方法
[1, 3, 4].toString()
"1,3,4"
[1, 3, 4] + ''
"1,3,4"
[1, 3, 4].join()
"1,3,4"
複製程式碼
  • 字串轉陣列 一種方法
'abxc'.split('')
(4) ["a", "b", "x", "c"]
複製程式碼

在map()的最後,要注意陣列的空位問題。 我們先看一個map()處理含有空位的陣列的奇怪現象

var f = function(n){ return n + 1 };
undefined
[1, , 2].map(f) 
(3) [2, empty, 3]
[1, undefined, 2].map(f)
(3) [2, NaN, 3]
[1, null, 2].map(f)
(3) [2, 1, 3]
複製程式碼

可以發現[1, , 2].map(f)空位未執行map()。map方法不會跳過undefined和null,但是會跳過空位。

null + 1 = 1
true + 1 = 2
false + 1 = 1
//好奇怪
複製程式碼
  • 用一個更直觀的例子來證明map方法會跳過空位
Array(2).map(function (){
  console.log('enter...');
  return 1;
})
(2) [empty × 2]
    length: 2
    __proto__: Array(0)
複製程式碼

本文一開始就講了Array[2]始構造了長度為2的空陣列,沒有列印出enter,說明未執行map()方法。

使用 map 方法處理陣列時,陣列元素的範圍是在 callback 方法第一次呼叫之前就已經確定了。在 map 方法執行的過程中:原陣列中新增加的元素將不會被 callback 訪問到;若已經存在的元素被改變或刪除了,則它們的傳遞到 callback 的值是 map 方法遍歷到它們的那一時刻的值;而被刪除的元素將不會被訪問到。


以上引入了陣列的空位(hole)概念,那什麼才是陣列的空位呢 var a= [1, , 2] 中間就是一個空位

var a= [1, , 2]
undefined
a
(3) [1, empty, 2]
    0: 1
    2: 2
    length: 3
    __proto__: Array(0)
a[1]
undefined
複製程式碼

可以看到,空位計入陣列長度,空位可讀取,但是這個空位的值是undefined。 delete命令可以刪除陣列內的一個元素

a
(3) [1, empty, 2]
delete a[0]
true
a
(3) [empty × 2, 2]
    2: 2
    length: 3
    __proto__: Array(0)
複製程式碼

delete命令刪除成功,返回true,但是length不變,說明空位可以被讀取到,所以用delete命令無法清空陣列。目前把陣列清空的唯一方法就是把length屬性改為0。 換句話說length屬性不能過濾空位。 當使用length屬性進行陣列遍歷時,一定要非常小心。

陣列的某個位置是空位,與某個位置是undefined,是不一樣的。 為什麼不一樣呢。

  • 如果是空位,使用陣列的forEach方法(接下來重點研究)、for...in結構、以及Object.keys方法進行遍歷,空位都會被跳過。
var a = [1, , , 5]
undefined
a
(4) [1, empty × 2, 5]
    0: 1
    3: 5
    length: 4
    __proto__: Array(0)
//只列印出了已經存在具體數值的1和5
a.forEach(function(x){console.log(x)})
1
5
undefined
//只有0索引和3索引
for (var i in a) {
  console.log(i);
}
0
3
undefined
//只有0索引和3索引
Object.keys(a)
(2) ["0", "3"]
    0: "0"
    1: "3"
    length: 2
    __proto__: Array(0)
複製程式碼
  • 如果是undefined,使用陣列的forEach方法(接下來重點研究)、for...in結構、以及Object.keys方法進行遍歷,不會被跳過。
var a = [undefined, undefined, undefined];

a.forEach(function (x, i) {
  console.log(i + '. ' + x);
});
// 0. undefined
// 1. undefined
// 2. undefined

for (var i in a) {
  console.log(i);
}
// 0
// 1
// 2

Object.keys(a)
// ['0', '1', '2']
複製程式碼

上面的對比可以知道,空位就是陣列沒有這個元素,所以不會被遍歷到,而undefined則表示陣列有這個元素,值是undefined,所以遍歷不會跳過。


5.3 forEach()

該方法與map()類似,都是使陣列的每個元素執行一個函式。與map()的最大區別是沒有返回值,而map()返回一個新的陣列。forEach()只關心資料的操作,而不關心返回值。forEach()方法傳入的函式,實際上是有3個值。 MDN的語法規範

array.forEach(callback(currentValue, index, array){
    //do something
}, this)

array.forEach(callback[, thisArg])
複製程式碼

引數列表的含義與map()方法的每個引數含義相同。 callback()函式的array引數,通常省略,自己要腦補上。

//x就是陣列的每一個元素,i是每一個元素的索引
arr.forEach(function(x, i){
	console.log(i + ': ' + x)
})
0: 1
1: 2
2: 3
複製程式碼

誰去呼叫的forEach()方法,那麼callback()裡面的array就會自動傳入那個陣列,但是是隱藏的。和我一樣的初學者,都曾懷疑過,哪裡傳進來的陣列呢,最好的答案都在MDN的callback()函式的語法規則裡面,具體的細節分析和map()的分析一樣。

  • 注意: 用forEach()方法遍歷陣列,無法再某個條件時停止遍歷,此時應該用普通的for迴圈
var arr1 = [1, 2, 3]
undefined
for (let i = 0; i < arr1.length; i++){
	if(arr1[i] === 2){break;}
	console.log(i)
}
0
複製程式碼

上面程式碼中,執行到陣列的第二個成員時,就會中斷執行。forEach方法做不到這一點。

  • 與map()方法一樣,forEach方法會跳過陣列的空位。而不會跳過undefined和null。
var log = function (n) {
 console.log(n + 1);
};

[1, undefined, 2].forEach(log)
// 2
// NaN
// 3

[1, null, 2].forEach(log)
// 2
// 1
// 3

[1, , 2].forEach(log)
// 2
// 3
複製程式碼
  • 當然了,forEach方法也可以用於類似陣列的物件和字串。
var obj = {
  0: 1,
  a: 'hello',
  length: 1
}

Array.prototype.forEach.call(obj, function (value, i) {
  console.log( i + ':' + value);
});
// 0:1

var str = 'hello';
Array.prototype.forEach.call(str, function (value, i) {
  console.log( i + ':' + value);
});
// 0:h
// 1:e
// 2:l
// 3:l
// 4:o
複製程式碼

物件和字串使用foreach一定要用Array.prototype.forEach.call()的。

forEach 遍歷的範圍在第一次呼叫 callback 前就會確定。呼叫forEach 後新增到陣列中的項不會被 callback 訪問到。如果已經存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。已刪除的項不會被遍歷到。如果已訪問的元素在迭代時被刪除了(例如使用 shift()) ,之後的元素將被跳過

ε=(´ο`*)))唉,上面這段話啊,可以看出forEach()和map()函式如此的相似啊。

  • 舉一個MDN上面的例子,一旦陣列被修改了,遍歷不受修改的影響
var words = ["one", "two", "three", "four"];
words.forEach(function(word) {
  console.log(word);
  if (word === "two") {
    words.push('aaa');
  }
});
one
two
three
four
複製程式碼

我們發現遍歷出了原來的所有元素,在forEach()開始之後的新增的'aaa'並不會遍歷到。 不過MDN的原始例子比我的難度大多了啊。

var words = ["one", "two", "three", "four"];
words.forEach(function(word) {
  console.log(word);
  if (word === "two") {
    words.shift();
  }
});
// one
// two
// four
複製程式碼

當到達包含值"two"的項時,整個陣列的第一個項被移除了,這導致所有剩下的項上移一個位置。因為元素 "four"現在在原陣列的第三個位置,three跑到了第二個位置,而此時要去遍歷第三個位置,所以不會列印three。

5.4 filter()

filter方法的引數是一個函式,所有陣列成員依次執行該函式,返回結果為true的成員組成一個新陣列返回。該方法不會改變原陣列。 通俗的理解就是過濾器。callback()函式與以上兩個一樣,也是傳入三個引數。 第一個引數是當前陣列成員的值,這個是必須的。

var arr = [1, 3, 5, 7]
undefined
arr.filter(function(value){return value>5})
[7]
arr.filter(function(value){return value>1})
(3) [3, 5, 7]
複製程式碼

可以理解為給filter()傳入的函式一個規則,滿足規則的才能返回。

5.5 reduce()

reduce() 方法對累加器和陣列中的每個元素(從左到右)應用一個函式,將其減少為單個值。

以上是MDN的解釋,挺難理解字面意思的。直接用例項來理解吧。

  • 累加求和
var arr = [1, 3, 10, 6] 
undefined
arr.reduce(function(preSum, ele){
	return preSum + ele;
})
20
複製程式碼

reduce()函式傳入一個函式作為引數,函式裡面傳入兩個引數,preSum預設是陣列的第一個元素,每次都把陣列的兩個元素相加並返回,ele就是每個陣列元素。 你也快成規定起始的累加值

arr.reduce(function(preSum, ele){
	return preSum + ele;
}, 10)
30
複製程式碼

起始的累加值是10,那麼加上陣列的20就是30。

  • 用reduce表示map()
var arr = [1, 3, 4]
undefined
arr.reduce(function(arr, n){
	arr.push(n*2)
	return arr
}, [])//[]空陣列作為一個初始值
(3) [2, 6, 8]
複製程式碼

利用reduce()完成了map()一樣的功能

  • 用reduce表示filter()
var arr = [1, 3, 4, 10, 30]
undefined
arr.reduce(function(arr, n){
	if(n>3){
		arr.push(n)
	}
	return arr
}, [])
(3) [4, 10, 30]
複製程式碼

如果原陣列裡面的值大於3,就放到新的陣列裡面。和filter()道理一樣。

  • 計算陣列裡面技術的和 var a = [1,2,3,4,5,6,7,8,9] 計算所有奇數的和
var a = [1,2,3,4,5,6,7,8,9]
a.reduce(function(sum, n){
	if(n % 2 === 0){
		return sum
    } else{
		return sum + n
	}
})
25
複製程式碼

先判斷一下,再把奇數相加

5.6 幾個方法組合使用

  • 計算陣列的偶數和 給定一個 陣列 var a = [1,2,3,4,5,6,7,8,9]
  1. 獲取所有偶數
  2. 得到所有偶數的平方
a.filter(function(n){
  if (n %2 ===0){
    return n
  }
}).map(function(n){
  return n*n
})//[4,16,36,64]
複製程式碼

先呼叫filter()獲得所有偶數,再呼叫map()獲得所有偶數平方和

5.7 some(),every()

some() 方法測試陣列中的某些元素是否通過由提供的函式實現的測試。 傳入的引數也是一個callback()函式,callback 被呼叫時傳入三個引數:元素的值,元素的索引,被遍歷的陣列。其實一般只要發現時傳入callback()函式,基本都是這些引數。

arr
(5) [1, 3, 4, 10, 30, notNumber: "not a number"]
    0: 1
    1: 3
    2: 4
    3: 10
    4: 30
    notNumber: "not a number"
    length: 5
    __proto__: Array(0)
arr.some(function(value, index){
	return index > 5
})
false
arr.some(function(value, index){
	return index > 3
})
true
複製程式碼

some()方法的作用是隻要陣列中的某個元素滿足傳入的函式的要求就返回true

every() 方法測試陣列的所有元素是否都通過了指定函式的測試。

var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
  return elem >= 3;
});
// false
複製程式碼

every()是要求陣列的所有元素都滿足傳入的函式的要求才返回true

  • 注意:對於空陣列,some方法返回false,every方法返回true,回撥函式都不會執行。
function isEven(x) { return x % 2 === 0 }
undefined

[].every(isEven)
true

[].some(isEven)
false
複製程式碼

對上面的結果,我又有什麼辦法呢,只能選擇背過唄。 這兩個方法都不改變原陣列

6. 上述陣列的方法的使用總結

陣列的上述方法種類繁多,不過有幾個特點很明顯,一些方法會改變原陣列,一些方法不會改變原陣列,我以這個細節把上述方法分類如下

6.1 改變原陣列的方法

方法名字 方法作用
push() 在元素末尾新增元素,返回新增新元素後的陣列長度
pop() 刪除陣列末尾的元素,返回刪除的那個元素。與push()方法一起模擬棧這個資料結構
shift() 刪除陣列的第一個元素,返回刪除的那個元素。與push()方法結合,模擬佇列這個資料結構
unshift() 在陣列的起始位置新增新元素,返回新增新元素後的陣列長度
reverse() 把陣列的每一個元素的位置互換,返回翻轉後的陣列
splice() 根據方法傳入的引數刪除原陣列的部分元素,返回被刪除的元素。可以用來拆分陣列
indexOf(),lastIndexOf() 返回括號裡面 的元素第一次出現和最後一次出現的位置。NaN元素無法獲得位置
sort() 預設按照陣列元素的Unicode碼點排序,可以自己傳入函式,規定排序準則

6.2 不改變原陣列的方法

方法名字 方法作用
join() 以某種形式把陣列的所有元素以字串的形式返回,預設以逗號分隔,返回生成的新陣列
concat() 專業合併陣列,把新陣列新增到舊陣列的後面,返回生成的新陣列
slice() 根據方法傳入的引數提取原陣列的部分,返回提取的這個新陣列也可以用來把偽陣列變成真陣列
map() 必須傳入一個callback()函式,陣列的每一個元素執行這個函式,返回執行回撥函式後的新陣列。該方法會跳過空位
forEach() 必須傳入一個callback()函式,陣列的每一個元素執行這個函式。沒有返回值,無法終止迴圈
filter() 必須傳入一個callback()函式,陣列的每一個元素執行這個函式,返回結果為true的成員組成一個新陣列返回
reduce() 對陣列中的每個元素(從左到右)應用一個函式,將其減少為單個值。具體理解看例子吧
some() 只要陣列中的某個元素滿足傳入的函式的要求就返回true
every() 陣列的所有元素都滿足傳入的函式的要求才返回true

正是因為以上的方法對原陣列不造成影響,所以我們可以組合使用filter()、map()先過濾再匹配。

6.3 陣列的遍歷

對於有序、無序的資料,我們有時候會希望獲得所有的key或者value,陣列對這個需求尤甚。 一般來說,陣列的遍歷有三種方法

  • for...in迴圈
var arr = [1, 3, 4, 10, 30]
undefined
for (var key in arr){
	console.log(arr[key])
}
1
3
4
10
30
複製程式碼
  • 切忌把arr[key]手抖寫成了arr.key。因為arr.key等同於arr['key'],很明顯陣列沒有這個名字叫key的鍵。 for...in迴圈有個弊端就是它會把非數字的索引也列印出來
arr
(5) [1, 3, 4, 10, 30]
arr.notNumber = 'not a number'
"not a number"
arr
(5) [1, 3, 4, 10, 30, notNumber: "not a number"]

for (var key in arr){
	console.log(key + ':' + arr[key])
}
0: 1
1: 3
2: 4
3: 10
4: 30
notNumber: not a number
複製程式碼

如果我們只關心陣列的數字索引,用傳統的下面的傳統for迴圈

  • 傳統for迴圈
arr
(5) [1, 3, 4, 10, 30, notNumber: "not a number"]
for (let i = 0; i < arr.length; i++){
	console.log(i + ':' + arr[i])
}
0:1
1:3
2:4
3:10
4:30
複製程式碼

這種方法其實是我們人為規定了只遍歷數字索引,O(∩_∩)O哈哈~

  • forEach()迴圈
arr
(5) [1, 3, 4, 10, 30, notNumber: "not a number"]
arr.forEach(function(value, index){
	console.log(index + ':' + value)
})
0:1
1:3
2:4
3:10
4:30
複製程式碼

這種方法也不會遍歷非數字的索引。

相關文章