把JavaScript標準庫之數組一網打盡

學習任何編程語言,數組都是繞不過去的坎,每一個編程語言都在其標準庫裏面內置了功能強大的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的、每一個元素爲空的數組。bash


以上的寫法有個小bug 雖然元素爲空,可是正常來講,索引應該存在的,可是事實是 索引居然不存在數據結構

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

索引0、一、2處是undefined,徹底合理,可是索引不存在,很奇怪app


並且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索引是元素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、二、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的解釋明白,只不過須要看個三、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
複製代碼

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

相關文章
相關標籤/搜索