数组常用方法详解

数组是一个最常见的数据结构,也是用来存储数目比较大的有效的工具,这里详细的介绍数组的一些基本的方法

  • 栈和队列
    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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章