數組是一個最常見的數據結構,也是用來存儲數目比較大的有效的工具,這裏詳細的介紹數組的一些基本的方法
- 棧和隊列
JavaScript
中的數組既可以表示棧也可以表示隊列,當使用pop
方法時會視爲棧,將棧頂也就是數組最後一個元素彈出,當使用shift
方法時會視爲隊列,將隊首也就是數組第一個元素出列。不管是pop
方法還是shift
方法,都會返回彈出(刪除的元素)
而添加數組元素的方法push
,unshift
,這兩個添加的方法的返回值是數組的長度
let arr = [1,2,3]
console.log(arr.push(4)) // 4 表示數組長度爲4
console.log(arr.unshift(0)) // 5 表示數組長度爲5
console.log(arr.pop()) // 4 返回最後一個元素4
console.log(arr.shift()) // 0 返回第一個元素0
sort
通過調用sort
方法可以給數組元素進行排序,但是這個方法有點特殊,sort
會調用每個數組元素的toString
方法,這會導致排序的結果和預期的有出入
let arr = [1,2,10,20,100,200]
console.log(arr.sort()) // [ 1, 10, 100, 2, 20, 200 ]
調用 sort
會將每個元素都轉爲字符串,最終會給 “1”, “2”, “10”, "20"“10”, “20”, “100”, “200” 排序,而我們知道,JavaScript
中兩個字符串比較,會逐個比較每個字符的字符串的編碼,如果當前字符編碼相同,則依次比較下一位,一旦某個字符大於另一個字符則直接返回結果,不會再往後比較。這裏之所以 10 排在 2 的前面,是因爲在比較 “10” 和 “2” 時,先比較第一位也就是字符串 1 和字符串 2,因爲字符串 2 的編碼大於字符串 1,所以就會直接退出比較,就會產生 “10” < “2” 的結果
解決這個奇怪的現象可以通過給 sort 方法傳入一個比較函數
let arr = [1,2,10,20,100,200]
console.log(arr.sort((a,b) => a - b)) // [ 1, 2, 10, 20, 100, 200 ]
sort
是一個高階函數,即支持傳入一個函數作爲參數,每次比較時都會調用傳入的參數,其中參數 a
和b
就是兩個準備進行比較的字符串,在上一章還提到過,如果兩個字符串相減會轉爲 Number 類型再進行計算,這樣就可以避免字符串類型比較造成的問題。同時如果傳入的函數返回值大於 0 ,則 b
會排在 a
前面,小於 0 則 a
會排在 b
前面,等於 0 則不變,根據這個特點可以控制返回數組是順序還是倒序
let arr = [1,2,10,20,100,200]
// [ 200, 100, 20, 10, 2, 1 ] 倒序數組
// 第一次比較時 b 爲 2,a 爲 1,由於返回值大於 0,所以 b 將排在 a 的前面,變成 [2,1],以此類推
// 注意是插入排序
console.log(arr.sort((a,b) => b - a))
sort 它會修改原數組,而不是新生成一個數組作爲返回值,使用前請考慮清楚是否需要對原數組進行拷貝
let arr = [1,2,10,20,100,200]
console.log(arr.sort((a,b) => b - a)) // [ 200, 100, 20, 10, 2, 1 ]
console.log(arr) // [ 200, 100, 20, 10, 2, 1 ] 原數組被修改了!
concat
concat
會將參數添加到數組末尾,不像sort
會修改原數組,它會創建一個當前數組的副本(淺拷貝),所以相對比較安全
另外如果參數包含數組,會給數組進行一層降維
let arr = [1, 2, 3]
console.log(arr.concat(4, [5, 6], [7, [8, 9]])) // [ 1, 2, 3, 4, 5, 6, 7, [ 8, 9 ] ]
console.log(arr) // [1,2,3]
當然現在更推薦使用 ES6
的擴展運算符,寫法更加簡潔,和concat
實現的功能類似,同樣也會淺拷貝數組
let arr = [1, 2, 3]
console.log([...arr, 4, ...[5, 6], ...[7, [8, 9]]]) // [ 1, 2, 3, 4, 5, 6, 7, [ 8, 9 ] ]
console.log(arr) // [1,2,3]
slice
slice
會基於參數來切割數組,它同樣會淺拷貝數組,當傳入不同參數會有不同功能- 不傳參數會直接返回一個淺拷貝後的數組
- 只有第一個參數時,會返回第一個參數的下標到數組最後的數組,參數超過最大下標則返回空數組
- 當傳入兩個參數時,會返回第一個參數至第二個參數 - 1 下標的數組
- 如果第二個參數小於第一個參數(非負數),返回空數組
- 參數含有負數則會加上數組長度再應用上述規則
slice
方法可以將類數組轉爲真正的數組
let arr = [1, 2, 3, 4, 5]
console.log(arr.slice()) // [1, 2, 3, 4, 5] 淺拷貝原數組
console.log(arr.slice(2)) // [3, 4, 5]
console.log(arr.slice(2, 3)) // [3]
console.log(arr.slice(2, 1)) // []
console.log(arr.slice(2, -1)) // [3, 4] 等同於 arr.slice(2, -1 + 5)
console.log(Array.prototype.slice.call({0: "a", length: 1})) // ["a"]
splice
splice
可以認爲是push, pop, unshift, shift
的結合,並且能夠指定插入/刪除的位置,非常強大,但它傳入參數也更爲複雜,所以一般只有在操作數組具體下標元素的時候纔會使用,同時它也會修改原數組,使用時請注意
同時splice
會返回一個數組,如果是使用它的刪除功能,則返回的數組中會包含被刪除的元素,來看一些比較特殊的例子
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(0, 1)) // [1]
console.log(arr) // [2, 3, 4, 5]
console.log(arr.splice(1)) // [3,4,5]
console.log(arr) // [2]
第一次調用 splice
會在數組下標爲 0 的位置刪除一個元素,它的返回值就是被刪除的元素 1,同時打印數組,會發現xsplice
修改了原來的數組,原數組的第一個元素被刪除了
第二次調用splice
只傳了一個參數,表示刪除從數組下標爲 1 的位置至數組最後一個元素,因爲此時數組爲 [2,3,4,5],所以刪除下標從 1 到最後的元素 3,4,5,並作爲 splice
的返回值,最後原數組就只包含元素 2 了
indexOf
indexOf
方法會返回參數在數組中的下標,不存在則返回 -1,一個特殊情況就是NaN
,如果使用indexOf
判斷NaN
是否在數組中,永遠會返回 -1
let arr = [1,2,3,4,5,NaN]
console.log(NaN) // -1
解決這個問題可以使用 ES6
的includes
方法,它會返回一個布爾值,而非目標元素下標,同時它可以判斷NaN
是否存在與目標數組中
let arr = [1,2,3,4,5,NaN]
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes(NaN)) // true
reverse
reverse
和sort
以及splice
一樣會修改原數組
let arr = [1,2,3,4,5]
console.log(arr.reverse()) // [5,4,3,2,1]
console.log(arr) // [5,4,3,2,1]
- 迭代方法
ES5
爲數組提供了 5 個迭代方法,它們都是高階函數,第一個參數是一個函數,第二個參數是函數的this
指向-
every
-
filter
-
forEach
-
map
-
some
這些api
日常用的非常多就不贅述了,只說一個小細節
對一個空數組無論參數中的函數返回什麼,調用some
都會返回false
, 調用every
都會返回true
-
let arr = []
console.log(arr.some(()=>{})) // false
console.log(arr.every(()=>{})) // true
reduce
reduce
也就是歸併方法,個人認爲是數組中最高級的使用方法,用的好可以實現一些非常強大的功能,這裏舉個的例子:多維數組扁平化
const flat = function (depth = 1) {
let arr = Array.prototype.slice.call(this)
if(depth === 0 ) return arr
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
// 需要用 call 綁定 this 值,否則會指向 window
return [...pre, ...flat.call(cur,depth-1)]
} else {
return [...pre, cur]
}
}, [])
}
關於 reduce
還有一個關於下標的注意點,當 reduce
只傳一個參數時,index
的下標是從 1 也就是數組第二個元素開始的(如果此時數組爲空會報錯),當 reduce
傳入第二個參數,會作爲遍歷的起始值,此時 index
的下標就從 0 也就是數組第一個元素開始
let arr = ["b", "c", "d", "e"]
arr.reduce((pre, cur, index) => {
console.log(index)
return pre + cur
})
// 1
// 2
// 3
arr.reduce((pre, cur, index) => {
console.log(index)
return pre + cur
}, "a")
// 0
// 1
// 2
// 3