學習任何程式語言,陣列都是繞不過去的坎,每個程式語言都在其標準庫裡面內建了功能強大的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 陣列的本質
由偽陣列的問題引出真正的陣列應該具備什麼特點
__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]
- 獲取所有偶數
- 得到所有偶數的平方
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
複製程式碼
這種方法也不會遍歷非數字的索引。