數組常用方法詳解

數組是一個最常見的數據結構,也是用來存儲數目比較大的有效的工具,這裏詳細的介紹數組的一些基本的方法

  • 棧和隊列
    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是一個高階函數,即支持傳入一個函數作爲參數,每次比較時都會調用傳入的參數,其中參數 ab 就是兩個準備進行比較的字符串,在上一章還提到過,如果兩個字符串相減會轉爲 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

解決這個問題可以使用 ES6includes方法,它會返回一個布爾值,而非目標元素下標,同時它可以判斷NaN 是否存在與目標數組中

let arr = [1,2,3,4,5,NaN]
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes(NaN)) // true
  • reverse
    reversesort以及 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章