数组在工作中使用频繁,但我们大多数情况下只使用到了部分特性,久而久之,对于数组其它的相关特性就变得模糊起来,不免有错漏不全的理解,这里花了一点时间,对最常用的数组做一些总结。
定义
数组的标准定义:一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量。
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
内置方法
- concat / slice / splice
- sort / reverse
- push / unshift
- pop / shift
- toString
- join
从已有的数组创建新的数组
concat / slice / splice
concat
合并两个或多个数组,返回合并结果
不改变原有数组,返回一个新的合并数组
arrayObject.concat(arrayX,arrayX,......,arrayX)
,arrayX
可以是值/数组对象
[1,2].concat(3,4); //[1, 2, 3, 4] |
slice
从现有的数组返回选定的元素组成的数组 不改变原有数组
arrayObject.slice(start,end)
start 和 end 定义了选取范围,正数从0开始,负数从-1开始,始终为左闭右开,也就是左边包含右边不包含
[1,2,3,4].slice(0,1);//[1] |
splice
方法向/从数组中添加/删除项目,然后返回被删除的项目组成的新数组 会改变原有数组
arrayObject.splice(index,howmany,items,...)
index 起始位置,可以是负数
howmany 要删除的个数,0为不删除
items 要添加的元素
var arr1 = [1,2,3,4]; |
数组排序
sort / reverse
sort
用于对数组的元素进行排序
将对原数组操作,会改变原数组
arrayObject.sort(sortFn)
sortFn 为函数,用于提供排序比较规则,返回值是正值,则第一个参数比第二个大,负值,则第1个比第2个小,零,则两个数相等
如果sortFn为空,则按照字符编码的顺序进行排序
[1,51,4,12,2,3].sort() //[1, 12, 2, 3, 4, 51] |
reverse
用于颠倒数组中元素的顺序 会改变原有的数组
[1,3,4,5].reverse() //[5, 4, 3, 1] |
为数组增减元素
- push 添加 元素至数组尾部,返回新的数组长度
- unshift 添加 元素至数组开头,返回新的数组长度
- pop 删除 尾部元素,返回删除的元素
- shift 删除 头部元素,返回删除的元素
都是直接对原数组操作,会改变原有数组
var arr = [1,2,3,4]; |
数组转字符串
toString / join
[1,2,3,4].toString() //"1,2,3,4" |
ES5新增
- Array.isArray()
- [].indexOf() / [].lastIndexOf()
- [].forEach / [].map()
- [].every() / [].some()
- [].filter()
- [].reduce() / [].reduceRight()
数组类型判断
自定义一个方法来判断是否为数组
function isArray(obj){ |
ES5新增 Array.isArray()
来判断是否为数组
Array.isArray([]);//true |
查找元素
indexOf(el, startIndex) / lastIndexOf(el, lastIndex)
没有查找到元素返回-1,查找到元素,返回查找到的第一个的索引值,lastIndexOf
为从后面查起
- startIndex 表示从哪个索引开始找,默认为第1个索引
- lastIndex 表示从倒数第几个索引开始找,默认为最后一个索引
[1,2,3,2,4].indexOf(2); //1 |
数据遍历
[].forEach() / [].map()
[].forEach(function(value, index, arr){}, context)
循环遍历数组中的每一个元素,处理函数中的参数依次为:当前元素、当前元素索引、原数组
forEach的第二个参数,可以指定处理函数中this的指向
var arr = [1,2,3]; |
jQuery中的 $.each(function(index, values, arr){})
回调中的参数第1个是索引,第2个是元素,$.map()
亦是如此
map(function(value, index, arr){})
与forEach类似,返回一个新的数组,不改变原有数组
var arr = [1,2,3]; |
map的回调处理函数一定要有返回值,如果没用,则会返回undefined
检测是否包含某元素
[].every(function(value, index, arr){}, context) / [].some(function(value, index, arr){}, context)
- 数组元素的逻辑判定,回调函数返回true/false
- some是所判定的元素只要数组中有一个符合条件就返回true
- ervery是数组中必须所有元素都符合条件就返回true,否则返回false
- 在空数组上调用every返回true,some返回false
console.log([1,2,3,4].some(v=>v>3)) //true |
数组过滤
[].filter(fn, context)
- 当遍历元素时,fn返回true,则返回这个元素,否则不返回,fn中返回值只要弱等于true或false即可,使用==比较
- filter返回一个新的结果数组,不改变原有数组
[1,2,3,4].filter(v=>v>2); //[3, 4] |
数组聚合
[].reduce(fn, initialValue) / [].reduceRight(fn, initialValue)
- 将数组元素聚合(合并)为一个元素
- fn 的参数依次为 (之前值、当前值、索引值、数组本身),
- initialValue 表示最初的值,如果不存在,初始值为数组第1个元素
reduceRight
与 reduce
用法一致,区别是从最后一个元素开始迭代
var res = [1,2,3,4].reduce(function(prev, curr, index, arr){ |
ES6新增
- Array.from(arrLike) //类数组转数组
- Array.of(…args) //将一组值转成数组
- [].copyWithin(target, start, end) //复制数组成员到指定位置,会覆盖原有成员
- [].find(fn) // 查找元素,返回元素本身
- [].findIndex(fn) // 查找元素返回元素索引,没有则返回-1
- [].includes() // 检测数组是否包含某元素,返回true/false
- [].fill(value, start, end) //填充数组
- [].keys() // 返回数组的key组成的数组
- [].values() // 返回数组的值组成的数组
- [].entries() // 返回数组索引和值组成的数组
数组转换
Array.from(arrLike) / Array.of(...args)
Array.from
可以将类数组(有length属性的Object/可遍历(iterable)的对象/Set对象/Map对象)转换为数组
let arrayLike = {'0':'a', '1':'b', '2':'c', length:3}; |
Array.of
用于将一组值转换为数组
使用 Array
的构造函数初始化数组时,如果参数是1个,那么会创建这个参数长度的数组,数组的每一项为 undefined
,如果参数是多个,那么会创建这多个参数组成的数组,所以参数的个数会导致结果的不一致,Array.of
正是弥补这一点的扩展
Array(2);//[,] |
数组元素复制、填充与查找
- [].copyWithin(target, start, end) 复制数组成员到指定位置,会覆盖原有成员
- [].fill(value, start, end) 填充数组
- [].find(fn) 查找元素,返回元素本身
- [].findIndex(fn) 查找元素返回元素索引,没有则返回-1
- [].includes(value, startIndex) 检测数组是否包含某元素,返回true/false
Array.prototype.copyWithin(target, start = 0, end = this.length)
copyWithin
的三个参数:
- target(必需):从该位置开始替换数据。
- start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
fill
的三个参数
- value:要填充的元素
- start: 从哪里开始填充
- end: 从哪里填充结束
// 将3号位复制到0号位 |
- find、findIndex 都是用于查找符合条件的元素,都可发现NaN,弥补了indexOf的不足
- includes 用于检测是否含有某个元素,返回 true/false
区别:
- includes 返回true或false,参数是要查找的元素,第2个参数会查找的起始索引,可以是负数
- find 返回的查找到的元素,没有则返回undefined
- findIndex 返回的是元素的索引值,没有则返回-1
[1,2,3,4].find(v=>v>3); //4 |
获取数组中的键和值
- [].keys() // 返回 key 组成的数组
- [].values() // 返回 value 组成的数组
- [].entries() // 返回 {key:value} 组成的数组
这三个函数都返回一个 iterator
遍历器对象,可以使用 for...of
循环遍历
let iter1 = [1,2,3,4].keys(); |
改变数组的方法
- splice
- shift / pop
- unshift / push
- reverse / sort
- copyWithin / fill
不改变数组的方法
- indexOf / lastIndexOf
- toString / join
- concat / slice
- some / every / filter
- map / forEach
- reduce / reduceRight
- keys / values / entries
- find / findIndex / includes
返回新数组的方法 concat / slice / map
返回iterator对象的方法 keys / values / entries
数组空位
空位表示没有任何值,也不等于 undefined
in
运算符可以检测数组的某个key是否含有值
0 in [undefined,undefined]; //返回true 0号位置有值 |
数组方法对空位的处理
- forEach(), filter(), every() 和some()都会忽略空位。
- map()会跳过空位,但会保留这个值
- join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
// forEach方法 忽略空位 |
数组乱序
- sort+random
- 快速随机排序
- 随机至新数组
sort+random
调用 sort
函数,随机返回 true
or false
来决定是否交换位置
var arr = [0,1,2,3,4,5,6,7,8,9]; |
这种方法打乱的数组中,越往后面最大数字出现的概率越高,所以并不稳定
改进: 由于原数组大数在后小数在前,会出现以上结果,如果随机将首尾交换将产生较理想的结果
var arr = [0,1,2,3,4,5,6,7,8,9]; |
快速随机排序
例:数组有5个元素
- 第一次从前4个元素中随机一个元素与第5个元素交换
- 第二次从前3个元素中随机一个元素与第4个元素交换
- 第三次从前2个元素中随机一个元素与第3个元素交换
- 第四次将第1个元素中与第2个元素交换
var arr = [0,1,2,3,4]; |
效率高,循环次数少,稳定均匀,时间复杂度O(n-1)
随机至新数组
每次随机从原数组抽取一个元素,添加到新数组,直到原数组元素个数为0
var arr = [0,1,2,3,4,5,6,7,8,9]; |
效率比快速随机差一点,时间复杂度O(n)
数组去重
- for+for
- for + indexOf
- filter + indexOf
- sort + filter
- splice
- HashTable
- from + Set (ES6)
for+for
创建一个新的数组,对原数组进行两层for循环遍历比较,新数组里如果存在,则跳出循环,进入下一个,如果没有,则将元素添加至新数组,
最后返回这个去重后的新数组
var arr1 = [1, '2', 9, 1, '1', 2, 4, 9]; |
for+indexOf
与上面的原理相同,如果结果数组中不存在,则添加,否则继续循环下一次,时间复杂度O(n)
var arr1 = [1, '2', 9, 1, '1', 2, 4, 9]; |
filter+indexOf
使用filter替代上面的for循环
因为indexOf是返回元素中数组中第一次出现的索引,所以后面重复的元素返回的索引是第一次出现的索引,而不等于当前的索引,所以不会被返回
Array.prototype.distinct = function(){ |
splice去重
双层for循环对比加splice实现,发现重复的值,则删除掉,再将length减1,
Array.prototype.distinct = function(){ |
速度慢,占用内存高
sort+filter
先利用sort排序(重复的值会被排到一起),第一个直接返回,从第二个元素开始,相邻两个元素比较,如果不相等,则返回后面这个元素
Array.prototype.distinct = function(){ |
Hash去重
- 将数组的元素做为Hash对象的键,如果Hash中不存在,则将元素添加到结果数组中,存在,则继续下一次循环
- 由于1和’1’在hash对象中的key是会被转换成字符串的,所以 1 和 ‘1’ 会被认为是同一个,这里使用
key = typeof(item) + item
来做为key值
Array.prototype.distinct = function(){ |
Hash去重的改进
- 当数组元素为Object复杂对象时,上面的方式就不适用了,如
{a:1} === {a:1} //false
- 将数组中的项序列化为字符串,做为Object的key,因为Oject的key值是字符串,1和‘1’会被认为是同一个键
var o = {}; |
ES6 的去重方法
Array.from()
将一个类数组转为数组,Set数据结构存储的元素是唯一的
Array.prototype.distinct = function(){ |
总结
上面几种去重方式,filter+indexOf相当较快,但不能去除复杂对象如{a:1}和{a:1}, hash去重最准确,但速度稍慢,for+splice效率最差,ES6的from+Set仍然不能对复杂对象去重
数组排序(跳转)
数组排序方法众多,可以有很多种算法,这里请参考 经典排序算法_集锦
reduce高级用法
reduce()
方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。
reduce() 函数接收2个参数(M: 必填, O: 可选):
- (M) 回调reducer 函数 处理先前的结算结果和下一个元素直到序列结束。
- (O) 初值 作为第一次调用回调时的第一个参数。
rr.reduce(function(prev, cur, index, arr) {});
- prev: 第一项的值或者上一次叠加的结果值
- cur: 当前会参与叠加的项
- index: 当前值的索引
- arr: 数组本身
// 当前的购物清单 |
计算字母在字符串中出现的次数
var result = 'abcawab'.split('').reduce(function(res, cur){ |
元素添加
- 尾部添加 效率: arr[arr.length] > push > concat
- 头部添加 效率:[1].concat(arr) > arr.unshift(1) (safari相反)
- 中间添加 arr.splice(arr.length/2, 0, 1)
多维数组扁平化
var arrs = [[1, 2],[3, 4, 5], [6, 7, 8, 9]]; |
清空数组
var a =[1,3]; a.length=0 与 a = []
两种清空数组的区别:
list=[]
将一个新的引用数组赋值给变量,其它的引用不受影响,以前的数组内容如果被引用的话,将存储在内存,会导致内存泄漏list.length
删除数组里的内容,将影响到其它引用
var foo = [1,2,3]; |
求平均值、最大值、最小值
Math
的 max
方法和 min
方法接收多个参数,分别返回参数中的最大值和最小值
Math.max(1,2,3,4); |
扩展阅读