把JavaScript標準庫之數(shù)組一網(wǎng)打盡

學習任何編程語言,數(shù)組都是繞不過去的坎,每個編程語言都在其標準庫里面內(nèi)置了功能強大的Array對象。通過參考阮一峰教程和MDN,我把數(shù)組常見的方法以及一些誤區(qū)總結(jié)如下,內(nèi)容較多,而且會繼續(xù)補充,希望這一篇文章可以把數(shù)組的知識一網(wǎng)打盡。

1. 數(shù)組的簡單概念

1.1 數(shù)組是什么呢?

編程總要和數(shù)據(jù)打交道,常見的普通的數(shù)據(jù)由基本數(shù)據(jù)類型可以定義,一些具有多重屬性、內(nèi)容的數(shù)據(jù)就需要復雜的數(shù)據(jù)類型去定義,也就是對象來定義,數(shù)組也是對象的一種。

  • 為了方便理解,我們可以認為數(shù)組是具有一定順序的復雜數(shù)據(jù)的組合(與對象的無序區(qū)別),每個位置對應一個索引,索引從0開始,具有l(wèi)ength屬性,而且length屬性是可變的

1.2 數(shù)組如何定義

  • 第一種方法是通過Array構(gòu)造函數(shù)來定義(該方法并不常用)
var arr1 = new Array(3)
undefined
arr1
(3) [empty × 3]
    length: 3
    __proto__: Array(0)

以上是控制臺打印結(jié)果,構(gòu)造了一個長度為3的、每個元素為空的數(shù)組。


以上的寫法有個小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不寫也是一樣的結(jié)果。

var arr2 = Array(3)
undefined
arr2
(3) [empty × 3]
    length: 3
    __proto__: Array(0)
  • 但是采用構(gòu)造函數(shù)的這種方法容易產(chǎn)生一些歧義,不同的參數(shù)個數(shù),會產(chǎn)生不同的結(jié)果。
1.2.1 構(gòu)造函數(shù)不寫參數(shù)
var arr3 = new Array
undefined
arr3
[]
    length:0
    __proto__:Array(0

此時構(gòu)造出空的數(shù)組,而且發(fā)現(xiàn)構(gòu)造函數(shù)的()寫不寫都可以

1.2.2 構(gòu)造函數(shù)寫1個正整數(shù)參數(shù)

那這個正整數(shù)參數(shù)就是構(gòu)造出來的數(shù)組的長度。

1.2.3 構(gòu)造函數(shù)參數(shù)是一個非正整數(shù)(字符串、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)

這個非正整數(shù)就是數(shù)組的內(nèi)容

1.2.4 構(gòu)造函數(shù)寫多個參數(shù)
var arr4 = new Array(1, 2)
undefined
arr4
(2) [1, 2]
    0: 1
    1: 2
    length: 2
    __proto__: Array(0)

此時直接構(gòu)造出0索引是元素1、1索引是元素2的數(shù)組對象。

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)

即多參數(shù)時,所有參數(shù)都是返回的新數(shù)組的成員

1.2.5 構(gòu)造函數(shù)參數(shù)是非正整數(shù),報錯
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 數(shù)組定義的正確方法

綜上所述,其實實際中直接用字面量定義數(shù)組

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

2. 數(shù)組的length屬性解疑

如果你是初學者,一定要記住數(shù)組的length屬性和里面有幾個元素無關(guān),愛幾個元素幾個元素,length并不是計數(shù)的作用。這是我自學是對數(shù)組長度最大的誤解。
正確的理解是:數(shù)組的length屬性等于最大正整數(shù)索引+1
而,數(shù)組的索引可以隨便改變,那么length屬性也是一個動態(tài)的值,可以變化。

var arr = []
undefined
arr
[]
    length:0
    __proto__:Array(0)
arr[10] = '我是第10個元素,我前面沒有元素,但是數(shù)組的長度絕對是11,你信不信'
"我是第10個元素,我前面沒有元素,但是數(shù)組的長度絕對是11,你信不信"
arr
(11) [empty × 10, "我是第10個元素,我前面沒有元素,但是數(shù)組的長度絕對是11,你信不信"]
    10:"我是第10個元素,我前面沒有元素,但是數(shù)組的長度絕對是11,你信不信"
    length:11
    __proto__:Array(0)

這個例子一開始是個空數(shù)組,長度是0,直接給他一個索引10,可以發(fā)現(xiàn)長度立馬變?yōu)?1。

arr[100] = '這次數(shù)組長度絕對是101'
"這次數(shù)組長度絕對是101"
arr.length
101

通過以上的例子,我們反向推理,把可以明白數(shù)組長度根本不連續(xù),是動態(tài)變化的,即數(shù)組長度是可寫的。唯一的不變真理是,它的長度永遠等于最大索引+1。

2.1 把數(shù)組清空的方法

有以上知識可以知道數(shù)組長度可以人為改變,進而大膽的猜想,改變長度會不會把數(shù)組清空呢?

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)

把數(shù)組length設(shè)為0,證明可以清空數(shù)組。

2.2 有趣的一點

由于數(shù)組本質(zhì)上是對象的一種,所以我們可以為數(shù)組添加屬性,但是這不影響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,只要你沒添加一個正整數(shù)的索引,無論你添加多少其他元素,長度永遠不會變化。

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

3. 偽數(shù)組(array-like object)

如果一個對象的所有鍵名都是正整數(shù)或零,并且有l(wèi)ength屬性,那么這個對象就很像數(shù)組,語法上稱為“類似數(shù)組的對象”

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"

上面的對象,看著結(jié)構(gòu)特別像但是絕對不是數(shù)組。
因為proto指向的就不是Array的prototype,沒有指向Array的共有屬性,再怎么想也只是模仿,本質(zhì)不同。不具備數(shù)組的其他方法(第四部分將要列舉的方法)。

3.1 數(shù)組的本質(zhì)

由偽數(shù)組的問題引出真正的數(shù)組應該具備什么特點

數(shù)組.png

proto必須指向數(shù)組的公有屬性才是真正的數(shù)組對象。

4. 數(shù)組實例的常見簡單的方法(可以無參或者參數(shù)很簡單)

4.1 判斷數(shù)組還是對象

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()方法可以判斷是不是數(shù)組對象,以前學過的instanceOf也可以判斷。

arr instanceof Array
true
obj instanceof Array
false

所以現(xiàn)在有兩個方法可以區(qū)分是數(shù)組還是對象了。

4.2 valueOf(),toString()

  • valueOf()返回數(shù)組本身
var arr = ['a', 'b']
undefined
arr.valueOf()
(2) ["a", "b"]
arr.toString()
"a,b"
  • toString()返回數(shù)組的字符串形式

4.3 push()

var arr = ['a', 'b']
undefined
arr.push('f')
3
arr
(3) ["a", "b", "f"]

向數(shù)組的末尾添加元素,返回添加成功后的數(shù)組的長度
會改變原數(shù)組

4.4 pop()

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

刪除數(shù)組的最后一個元素,并返回刪除的這個元素。

[].pop() // undefined
  • 注意:對空數(shù)組使用pop方法,不會報錯,而是返回undefined。
    這個方法會改變原數(shù)組

push() 和pop()方法一起使用可以模擬棧的數(shù)據(jù)結(jié)構(gòu)


4.5 join()

以某種形式把數(shù)組的所有成員以字符串的形式返回

arr
(2) ["a", "b"]
arr.join('-')
"a-b"

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

arr.join()
"a,b"

如果沒有規(guī)定格式,則以逗號分隔

var arr = ['a', 'rr', null, undefined]
undefined
arr
(4) ["a", "rr", null, undefined]
arr.join()
"a,rr,,"
  • 注意:如果字符串中有null和undefined的,會被轉(zhuǎn)成空字符串。
    該方法不會改變原數(shù)組

4.6 concat()

是一個專業(yè)合并數(shù)組的方法。

var arr = ['a', 'rr', null, undefined]
undefined
arr.concat(['rrr'])
(5) ["a", "rr", null, undefined, "rrr"]
arr
(4) ["a", "rr", null, undefined]

把一個新數(shù)組添加到舊數(shù)組的后面,返回生成的新數(shù)組。
不會改變原數(shù)組

4.7 shift()

刪除數(shù)組的第一個元素,并返回刪除的那個元素

arr
(4) ["a", "rr", null, undefined]
arr.shift()
"a"
arr
(3) ["rr", null, undefined]

會改變原數(shù)組


push()與shift()方法結(jié)合,可以模擬隊列的數(shù)據(jù)結(jié)構(gòu)


4.8 unshift()

在數(shù)組的第一個位置添加元素,并返回添加新元素后的數(shù)組長度

arr
(3) ["rr", null, undefined]
arr.unshift('ffff')
4
arr
(4) ["ffff", "rr", null, undefined]

和shift()方法的作用正好相反。
一定會改變原數(shù)組

4.9 reverse()

反轉(zhuǎn)數(shù)組,返回反轉(zhuǎn)后的數(shù)組

arr
(4) ["ffff", "rr", null, undefined]
arr.reverse()
(4) [undefined, null, "rr", "ffff"]
arr
(4) [undefined, null, "rr", "ffff"]

會改變原數(shù)組

4.10 slice()

提取原數(shù)組的一部分,返回一個新的數(shù)組

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()無參是原數(shù)組的拷貝
arr.slice(1)從索引為1的位置開始截取,到末尾。

var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]

如果slice方法的參數(shù)是負數(shù),則表示倒數(shù)計算的位置。
上面代碼中,-2表示倒數(shù)計算的第二個位置,-1表示倒數(shù)計算的第一個位置。


slice()方法可以吧偽數(shù)組變成真的數(shù)組


不會改變原數(shù)組

4.11 splice()

刪除原數(shù)組的一部分成員,返回被刪的元素。

arr
(4) [undefined, null, "rr", "ffff"]
arr.splice(1, 3)
(3) [null, "rr", "ffff"]
arr
[undefined]

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

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
同樣的負數(shù)表示倒數(shù)第幾個位置
會改變原數(shù)組
splice()有兩個變式

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

把第二個參數(shù)設(shè)為0,就可以在第2個位置插入一個元素了

  • 變式2:我只給一個參數(shù),就是拆分數(shù)組,為兩個新數(shù)組
a
(5) [1, 1, 1111, 1, 10]
a.splice(2)
(3) [1111, 1, 10]
a
(2) [1, 1]

a.splice(2)從第三個索引處拆分這個數(shù)組。

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(),返回括號里面 的元素第一次出現(xiàn)的位置。
如果有兩個參數(shù)則是表示搜索的位置從第二個參數(shù)開始。
如果找不到該元素,則返回-1。
lastIndexOf()返回括號里面的元素最后一次出現(xiàn)的位置。

  • 一個MDN的實戰(zhàn)例子:獲得數(shù)組里面某個元素出現(xiàn)的所有位置(利用循環(huán)和返回值-1的特點)
var arr = ['a', 0, 'a', 'b', 'a'];
var arrTemp = []; //空數(shù)組用來存儲目標元素出現(xiàn)的所有索引
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'出現(xiàn)在0、2、4索引位置處

注意:這里有個例外
數(shù)組里面包含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數(shù)組的第四個位置是NaN,但是無法獲得索引。
因為indexOf(),lastIndexOf()是嚴格按照===操作符來檢測的,而NaN是唯一的不與自身相等的值。

NaN === NaN
false
1 === 1
true
'a' === 'a'
true

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


5. 數(shù)組實例的常見復雜的方法(參數(shù)是另一個函數(shù))

5.1 sort()

下面MDN的解釋非常棒

sort() 方法在適當?shù)奈恢脤?shù)組的元素進行排序,并返回數(shù)組。 sort 排序不一定是穩(wěn)定的。默認排序順序是根據(jù)字符串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中, 數(shù)字在大寫字母之前,
// 大寫字母在小寫字母之前.

上述代碼兩點注意

  • 第一點是
    上述代碼中的第二部分的[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方法明顯的會改變原數(shù)組啊
  • 我們通常不想使用默認的升序排列,sort方法可以傳入函數(shù)來改變順序。
    MDN的語法是arr.sort(compareFunction)
    compareFunction這個函數(shù)用來指定按某種順序進行排列的函數(shù)。如果省略,元素按照轉(zhuǎn)換為的字符串的諸個字符的Unicode位點進行排序。
    compareFunction這個函數(shù)基本的規(guī)則是傳入兩個參數(shù)
function compareNumbers(a, b) {
  return a - b;
}
a,b參數(shù)比較 代表的意思
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]

降序排列。

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

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

5.2 map()

map() 方法創(chuàng)建一個新數(shù)組,其結(jié)果是該數(shù)組中的每個元素都調(diào)用一個提供的函數(shù)后返回的結(jié)果。
不影響原數(shù)組。

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

以上代碼中map()方法里面?zhèn)魅氲暮瘮?shù)是一個把數(shù)組每個值都加上一個'f'。
每個元素末尾都加上一個'f',然后返回這個新的數(shù)組,原數(shù)組沒有任何變化的。
我初學的時候,看到上述代碼反正很懵逼,這玩意咋出來的這個結(jié)果呢。琢磨了很久,還是覺得MDN的解釋明白,只不過需要看個3、4遍就能明白了。
語法規(guī)范是:

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

callback
生成新數(shù)組元素的函數(shù),使用三個參數(shù):
currentValue
callback 的第一個參數(shù),數(shù)組中正在處理的當前元素。
index
callback 的第二個參數(shù),數(shù)組中正在處理的當前元素的索引。
array
callback 的第三個參數(shù),map 方法被調(diào)用的數(shù)組。
thisArg
可選的。執(zhí)行 callback 函數(shù)時 使用的this 值。
返回值
一個新數(shù)組,每個元素都是回調(diào)函數(shù)的結(jié)果。

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

其實callback 的第三個參數(shù)可以不寫,也知道調(diào)用的到底是哪個Array。

[1, 2, 3].map(function(currentValue, index){
    return currentValue*index
})
(3) [0, 2, 6]

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

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

以上是通過map函數(shù)的call方法間接使用

  • 第二種轉(zhuǎn)換方法
'abc'.split('').map(upper)
(3) ["A", "B", "C"]

'abc'.split('')把字符串轉(zhuǎn)成數(shù)組["a", "b", "c"]


至此,字符串和數(shù)組相互轉(zhuǎn)化的方法,都學到了,總結(jié)如下。

  • 數(shù)組轉(zhuǎn)字符串 三種方法
[1, 3, 4].toString()
"1,3,4"
[1, 3, 4] + ''
"1,3,4"
[1, 3, 4].join()
"1,3,4"
  • 字符串轉(zhuǎn)數(shù)組 一種方法
'abxc'.split('')
(4) ["a", "b", "x", "c"]

在map()的最后,要注意數(shù)組的空位問題。
我們先看一個map()處理含有空位的數(shù)組的奇怪現(xiàn)象

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]

可以發(fā)現(xiàn)[1, , 2].map(f)空位未執(zhí)行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]始構(gòu)造了長度為2的空數(shù)組,沒有打印出enter,說明未執(zhí)行map()方法。

使用 map 方法處理數(shù)組時,數(shù)組元素的范圍是在 callback 方法第一次調(diào)用之前就已經(jīng)確定了。在 map 方法執(zhí)行的過程中:原數(shù)組中新增加的元素將不會被 callback 訪問到;若已經(jīng)存在的元素被改變或刪除了,則它們的傳遞到 callback 的值是 map 方法遍歷到它們的那一時刻的值;而被刪除的元素將不會被訪問到。


以上引入了數(shù)組的空位(hole)概念,那什么才是數(shù)組的空位呢
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

可以看到,空位計入數(shù)組長度,空位可讀取,但是是undefined。
delete命令可以刪除數(shù)組內(nèi)的一個元素

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命令無法清空數(shù)組。目前把數(shù)組清空的唯一方法就是把length屬性改為0。
換句話說length屬性不能過濾空位
當使用length屬性進行數(shù)組遍歷時,一定要非常小心。

數(shù)組的某個位置是空位,與某個位置是undefined,是不一樣的。
為什么不一樣呢。

  • 如果是空位,使用數(shù)組的forEach方法(接下來重點研究)、for...in結(jié)構(gòu)、以及Object.keys方法進行遍歷,空位都會被跳過。
var a = [1, , , 5]
undefined
a
(4) [1, empty × 2, 5]
    0: 1
    3: 5
    length: 4
    __proto__: Array(0)
//只打印出了已經(jīng)存在具體數(shù)值的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,使用數(shù)組的forEach方法(接下來重點研究)、for...in結(jié)構(gòu)、以及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']

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


5.3 forEach()

該方法與map()類似,都是使數(shù)組的每個元素執(zhí)行一個函數(shù)。與map()的最大區(qū)別是沒有返回值,而map()返回一個新的數(shù)組。forEach()只關(guān)心數(shù)據(jù)的操作,而不關(guān)心返回值。forEach()方法傳入的函數(shù),實際上是有3個值。
MDN的語法規(guī)范

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

array.forEach(callback[, thisArg])

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

//x就是數(shù)組的每一個元素,i是每一個元素的索引
arr.forEach(function(x, i){
    console.log(i + ': ' + x)
})
0: 1
1: 2
2: 3

誰去調(diào)用的forEach()方法,那么callback()里面的array就會自動傳入那個數(shù)組,但是是隱藏的。和我一樣的初學者,都曾懷疑過,哪里傳進來的數(shù)組呢,最好的答案都在MDN的callback()函數(shù)的語法規(guī)則里面,具體的細節(jié)分析和map()的分析一樣。

  • 注意: 用forEach()方法遍歷數(shù)組,無法再某個條件時停止遍歷,此時應該用普通的for循環(huán)
var arr1 = [1, 2, 3]
undefined
for (let i = 0; i < arr1.length; i++){
    if(arr1[i] === 2){break;}
    console.log(i)
}
0

上面代碼中,執(zhí)行到數(shù)組的第二個成員時,就會中斷執(zhí)行。forEach方法做不到這一點。

  • 與map()方法一樣,forEach方法會跳過數(shù)組的空位。而不會跳過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方法也可以用于類似數(shù)組的對象和字符串。
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 遍歷的范圍在第一次調(diào)用 callback 前就會確定。調(diào)用forEach 后添加到數(shù)組中的項不會被 callback 訪問到。如果已經(jīng)存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。已刪除的項不會被遍歷到。如果已訪問的元素在迭代時被刪除了(例如使用 shift()) ,之后的元素將被跳過

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

  • 舉一個MDN上面的例子,一旦數(shù)組被修改了,遍歷不受你的影響
var words = ["one", "two", "three", "four"];
words.forEach(function(word) {
  console.log(word);
  if (word === "two") {
    words.push('aaa');
  }
});
one
two
three
four

我們發(fā)現(xiàn)遍歷出了原來的所有元素,在forEach()開始之后的添加的'aaa'并不會遍歷到。
不過MDN的例子比我的難度大多了啊。

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

當?shù)竭_包含值"two"的項時,整個數(shù)組的第一個項被移除了,這導致所有剩下的項上移一個位置。因為元素 "four"現(xiàn)在在原數(shù)組的第三個位置,three跑到了第二個位置,而此時要去遍歷第三個位置,所以不會打印three。

5.4 filter()

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

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()傳入的函數(shù)一個規(guī)則,滿足規(guī)則的才能返回。

5.5 reduce()

reduce() 方法對累加器和數(shù)組中的每個元素(從左到右)應用一個函數(shù),將其減少為單個值。
以上是MDN的解釋,挺難理解字面意思的。直接用實例來理解吧。

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

reduce()函數(shù)傳入一個函數(shù)作為參數(shù),函數(shù)里面?zhèn)魅雰蓚€參數(shù),preSum默認是數(shù)組的第一個元素,每次都把數(shù)組的兩個元素相加并返回,ele就是每個數(shù)組元素。
你也快成規(guī)定起始的累加值

arr.reduce(function(preSum, ele){
    return preSum + ele;
}, 10)
30

起始的累加值是10,那么加上數(shù)組的20就是30。

  • 用reduce表示map()
var arr = [1, 3, 4]
undefined
arr.reduce(function(arr, n){
    arr.push(n*2)
    return arr
}, [])//[]空數(shù)組作為一個初始值
(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]

如果原數(shù)組里面的值大于3,就放到新的數(shù)組里面。和filter()道理一樣。

  • 計算數(shù)組里面技術(shù)的和
    var a = [1,2,3,4,5,6,7,8,9]
    計算所有奇數(shù)的和
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

先判斷一下,再把奇數(shù)相加

5.6 幾個方法組合使用

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

先調(diào)用filter()獲得所有偶數(shù),再調(diào)用map()獲得所有偶數(shù)平方和

5.7 some(),every()

some() 方法測試數(shù)組中的某些元素是否通過由提供的函數(shù)實現(xiàn)的測試。
傳入的參數(shù)也是一個callback()函數(shù),callback 被調(diào)用時傳入三個參數(shù):元素的值,元素的索引,被遍歷的數(shù)組。其實一般只要發(fā)現(xiàn)時傳入callback()函數(shù),基本都是這些參數(shù)。

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()方法的作用是只要數(shù)組中的某個元素滿足傳入的函數(shù)的要求就返回true

every() 方法測試數(shù)組的所有元素是否都通過了指定函數(shù)的測試。

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

every()是要求數(shù)組的所有元素都滿足傳入的函數(shù)的要求才返回true

  • 注意:對于空數(shù)組,some方法返回false,every方法返回true,回調(diào)函數(shù)都不會執(zhí)行。
function isEven(x) { return x % 2 === 0 }
undefined

[].every(isEven)
true

[].some(isEven)
false

對上面的結(jié)果,我又有什么辦法呢,只能選擇背過唄。
這兩個方法都不改變原數(shù)組

6. 上述數(shù)組的方法的使用總結(jié)

數(shù)組的上述方法種類繁多,不過有幾個特點很明顯,一些方法會改變原數(shù)組,一些方法不會改變原數(shù)組,我以這個細節(jié)把上述方法分類如下

6.1 改變原數(shù)組的方法

方法名字 方法作用
push() 在元素末尾添加元素,返回添加新元素后的數(shù)組長度
pop() 刪除數(shù)組末尾的元素,返回刪除的那個元素。與push()方法一起模擬棧這個數(shù)據(jù)結(jié)構(gòu)
shift() 刪除數(shù)組的第一個元素,返回刪除的那個元素。與push()方法結(jié)合,模擬隊列這個數(shù)列這個數(shù)據(jù)結(jié)構(gòu)
unshift() 在數(shù)組的起始位置添加新元素,返回添加新元素后的數(shù)組長度
reverse() 把數(shù)組的每一個元素的位置互換,返回翻轉(zhuǎn)后的數(shù)組
splice() 根據(jù)方法傳入的參數(shù)刪除原數(shù)組的部分元素,返回被刪除的元素。可以用來拆分數(shù)組
indexOf(),lastIndexOf() 返回括號里面 的元素第一次出現(xiàn)和最后一次出現(xiàn)的位置。NaN元素無法獲得位置
sort() 默認按照數(shù)組元素的Unicode碼點排序,可以自己傳入函數(shù),規(guī)定排序準則

6.2 不改變原數(shù)組的方法

方法名字 方法作用
join() 以某種形式把數(shù)組的所有元素以字符串的形式返回,默認以逗號分隔,返回生成的新數(shù)組
concat() 專業(yè)合并數(shù)組,把新數(shù)組添加到舊數(shù)組的后面,返回生成的新數(shù)組
slice() 根據(jù)方法傳入的參數(shù)提取原數(shù)組的部分,返回提取的這個新數(shù)組也可以用來把偽數(shù)組變成真數(shù)組
map() 必須傳入一個callback()函數(shù),數(shù)組的每一個元素執(zhí)行這個函數(shù),返回執(zhí)行回調(diào)函數(shù)后的新數(shù)組。該方法會跳過空位
forEach() 必須傳入一個callback()函數(shù),數(shù)組的每一個元素執(zhí)行這個函數(shù)。沒有返回值,無法終止循環(huán)
filter() 必須傳入一個callback()函數(shù),數(shù)組的每一個元素執(zhí)行這個函數(shù),返回結(jié)果為true的成員組成一個新數(shù)組返回
reduce() 對數(shù)組中的每個元素(從左到右)應用一個函數(shù),將其減少為單個值。具體理解看例子吧
some() 只要數(shù)組中的某個元素滿足傳入的函數(shù)的要求就返回true
every() 數(shù)組的所有元素都滿足傳入的函數(shù)的要求才返回true

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

6.3 數(shù)組的遍歷

對于有序無序的數(shù)據(jù),我們有時候會希望獲得所有的key或者value,數(shù)組對這個需求尤甚。
一般來說,數(shù)組的遍歷有三種方法

  • for...in循環(huán)
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'],很明顯數(shù)組沒有這個名字叫key的鍵。
    for...in循環(huán)有個弊端就是它會把非數(shù)字的索引也打印出來
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

如果我們只關(guān)心數(shù)組的數(shù)字索引,用傳統(tǒng)的下面的傳統(tǒng)for循環(huán)

  • 傳統(tǒng)for循環(huán)
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

這種方法其實是我們?nèi)藶橐?guī)定了只遍歷數(shù)字索引,O(∩_∩)O哈哈~

  • forEach()循環(huán)
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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容