数组是一个最常见的数据结构,也是用来存储数目比较大的有效的工具,这里详细的介绍数组的一些基本的方法
- 栈和队列
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 了
:splice()方法始终都会返回一个数组,该数组中包含从原数组中删除的项(如果没有删除任何项,则返回一个空数组)
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