前言
作为一名标准的前端菜鸟,在日常开发里突然忘了数组的某个API方法是家常便饭了。
找时间对前端日常开发常用的JS数组API进行了梳理总结。方便自己复习,也方便大伙进行学习查阅。
有误之处欢迎讨论指正,本人积极接受批评(套盾)
构造函数、静态方法
Array 构造函数
new Array()用于创建一个新的数组,它根据参数长度的不同,会有两种不同的处理方式:
- new Array(arg1,arg2,...) 即参数个数为0/参数个数大于2时,会按照顺序一次成为新数组的0-n项(当然参数长度为0时返回空数组)
- new Array(arg) 即仅有一个参数时,如果该参数不是数字类型时,处理同上,返回一个只包含arg一项的数组;当该参数是数字类型时,创建一个长度为该参数的空数组,比如new Array(7)就代表创建了一个长度为7的空数组。注意:该数值最大不能超过32位无符号整型,即需要小于2的32次方,否则将抛出RangeError。
js
// 得益于Array构造器内部对this指针的判断 new Array()可以写成Array() 并不会有什么影响
const arrayA = Array(3)
// [empty x 3]
const arrayB = Array(1,2,3)
// [1,2,3]
const arrayC = Array('3')
// ['3']
const arrayD = Array('string')
// ['string']
Array.of
与Array构造器在使用上的区别基本上就在于处理单个数字
js
const arrayA = new Array(3)
// [empty x 3]
const arrayB = Array.of(3)
// [3]
因此需要使用数组来包裹元素的场景一般更推荐使用Array.of()
Array.from
只要一个对象有迭代器,Array.from就能用它生成一个新数组。(用mdn的说法是 "从可迭代或类数组对象创建一个新的浅拷贝的数组实例")
js
// String
Array.from('abc');
// ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def']));
// ["abc", "def"]
// Map
Array.from(new Map([[1, 'abc'], [2, 'def']]));
// [[1 , 'abc'], [2, 'def']]
// 天生的类数组对象arguments
function fn(){ return Array.from(arguments); } fn(1, 2, 3);
// [1, 2, 3]
那些会改变原数组的方法
删除 shift() 与 pop()
这里将shift()方法与pop()方法一同说明:
shift()方法删除数组的第一个元素,并返回这个元素。如果是栈的话,这个过程就是栈底弹出。
pop() 方法删除数组的最后一个元素,并且返回这个元素。如果是栈的话,这个过程就是栈顶弹出。
js
const arr = [1,2,3,4,5,6]
arr.shift() // 这里的返回值就是1
// 此时arr就变成了 [2, 3, 4, 5, 6]
arr.pop() // 这里的返回值就是6
// 此时arr就变成了 [2, 3, 4, 5]
受益于鸭式辨型,shift()和pop()方法同样可以应用在类数组对象上,同样的,Array.prototype 的所有方法均具有鸭式辨型这种神奇的特性。它们不止可以用来处理数组对象,还可以处理类数组对象。
鸭式辨型(Duck Typing):一种程序设计中的动态类型检查策略,来源于"如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子"这一俗语。在程序设计领域中,这个概念指的是判断一个对象是什么类型的依据不是看其本身属于哪个类或者接口,而是看它能提供什么样的行为或方法。
增加 unshift()与push()
unshift() 方法用于在数组头部插入一些元素(就像是栈底插入),并返回数组新的长度。如果是栈的话,这个过程就是栈底推入。
push()方法添加一个或者多个元素到数组末尾,并且返回数组新的长度。如果是栈的话,这个过程就是栈顶压入。
js
const arr = [1,2,3]
arr.unshift(5,6) // 返回值为 5
//此时arr为[5, 6, 1, 2, 3]
arr.push(7,8) // 返回值为 7
// 此时arr为[5, 6, 1, 2, 3, 7, 8]
先删后加 splice()
splice()方法可以用新元素来替换旧元素的办法来对数组进行修改。
其语法为arr.splice(start, deleteCount, item1, item2, itemN)
start 从 0 开始计算的索引,表示要开始改变数组的位置,它会被转化为整数
- 负索引从数组末尾开始计算------如果
start < 0
,使用start + array.length
。 - 如果
start < -array.length
,使用0
。 - 如果
start >= array.length
,则不会删除任何元素,但是该方法会表现为添加元素的函数,添加所提供的那些元素。 - 如果
start
被省略了(即调用splice()
时不传递参数),则不会删除任何元素。这与传递undefined
不同,后者会被转换为0
。
方便记忆,可以视作start为正数时对应的是数组元素索引值index,start为1就是从索引值为1的元素开始操作; start为负数时,start为-1就是从数组的倒数第一项开始进行操作,如果start这个绝对值比数组长度还大,那么就从索引值为0的一项开始操作
deleteCount 指定要删除的元素个数。
itemN 指定新增的元素,如果缺省,则该方法只删除数组元素。
是不是好像有点复杂?
说的直白点就是先找到start的位置,然后从start的位置根据deleteCount的值删掉对应数量的元素,deleteCount是1就删一个,是2就删两个。在删完后加上itemN的值。
灵活变换deleteCount和itemN的值即可使用splice完成增删改的操作。
忘了说明 splice的返回值是原数组中被删除元素组成的数组,如果没有删除,则返回一个空数组。
下面就举几个例子
js
// 增
var arr = [1,2,3]
var result = arr.splice(1,0,3) // 从index为1的一项开始操作,删除0项后添加一个元素3
// result的值为 [] (因为没删任何元素)
// 此时arr为 [1, 3, 2, 3]
// 删
arr = [1,2,3,4]
result = arr.splice(1,1) // 从index为1的一项开始操作,删除1项,什么都不添加
// result的值为 [2]
// 此时arr为 [1,3,4]
// 改
arr = [1,2,3,4]
result = arr.splice(2,1,0) // 从index为2的一项开始操作,删除1项后添加一个元素0
// result的值为 [3]
// 此时arr为 [1,2,0,4]
// 当start > arr.length时
arr = [1,2,3,4]
result = arr.splice(8,1,0) // 从index为8的一项开始操作,删除1项后添加一个元素0
// result值为 [] 因为并没有index为8的一项,删了个寂寞
// 此时arr为 [1,2,3,4,0] 虽然删了个寂寞但是添加还是正常添加
// 当start < 0 时
arr = [1,2,3,4]
result = arr.splice(-1,1,0) // 从倒数第一项开始操作,删除1项后添加一个元素0 操作还是按正常顺序操作的
// result值为 [4]
// 此时arr为 [1,2,3,0]
Tips: splice方法一样受益于鸭式辨型
排序 sort()
sort()方法对数组进行排序,其语法为arr.sort([comparefn])。
其中comparefn是可选的,如果省略comparefn,sort()方法会先将元素转换为字符串,然后根据字符串的Unicode码点顺序进行升序排列。(当然这样会产生一些问题,比如'10'会比'3'更靠前,'C'会比'a'更靠前,所以一般不建议省略comparefn)
如果指明了comparefn,那么数组将按照调用comparefn函数的返回值来进行排序,比如现在有一个数组[a,b],其中a和b是要被比较的元素,那么就会有如下情况:
- comparefn(a, b) < 0,那么a 将排到 b 前面;
- comparefn(a, b) = 0,那么a 和 b 相对位置不变;
- comparefn(a, b) > 0,那么b 将排到 a 前面;
sort()
方法保留空槽。如果源数组是稀疏的,则空槽会被移动到数组的末尾,并始终排在所有 undefined
元素的后面。
举个例子,如果你想将一个数组进行排序
js
var arr = [1,7,2,5,3,6,1,10,20,30]
arr.sort() // 省略comparefn
console.log(arr) // [1, 1, 10, 2, 20, 3, 30, 5, 6, 7]
arr.sort((a,b) => a-b) // 从小到大排序
console.log(arr) // [1, 1, 2, 3, 5, 6, 7, 10, 20, 30]
arr.sort((a,b) => b-a) // 从大到小排序
console.log(arr) // [30, 20, 10, 7, 6, 5, 3, 2, 1, 1]
当然,如果数组元素为非ASCII字符的字符串(如包含类似 e、é、è、a、ä 或中文字符等非英文字符的字符串),则需要使用String.localeCompare(即返回两个字符串在排序顺序中的比较结果)来完成。
举个例子
js
var arr = ['菜','鸟','的','自','我','救','赎']
// 如果省略方法直接排序
arr.sort()
// 按照数组元素unicode字符串形式进行排序
console.log(arr) // ['我', '救', '的', '自', '菜', '赎', '鸟']
//如果使用localeCompare来作为comparefn的比较逻辑
arr.sort((a,b) => a.localeCompare(b))
// 实际上是拼音先后顺序
console.log(arr) // ['菜', '的', '救', '鸟', '赎', '我', '自']
逆转 reverse()
reverse()方法颠倒数组中元素的位置,第一个会成为最后一个,最后一个会成为第一个,该方法返回对数组的引用。
这个方法很简单,没有参数,直接使用即可。
js
var arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.reverse()
console.log(arr) // [8, 7, 6, 5, 4, 3, 2, 1]
选中替换 copyWithin()
copyWithin()是es6新增的数组方法,它可以选中数组中的片段并把它替换到任意位置上。
语法为arr.copyWithin(target,start[,end])
target表示被替换的目标起始位置。
start表示截取片段的起始位置。如果start为负数,则选中start+length的元素。
end表示截取片段的的结束位置,可省略,如果省略则默认为arr.length(start和end为左闭右开区间),end为负数的情况同start。
copyWithin()方法的返回值为替换后的数组,需要注意的是copyWithin()方法永远不会改变数组的长度。
可以想象成现在有一个数组是'abcde',你鼠标拖动选中了'cde',按下ctrl+c,然后再选中'abc'然后按下ctrl+v。
举例
js
var arr = ['a','b','c','d','e']
arr.copyWithin(0,1,2) // 代表要被替换的目标起始位置是0 截取索引值[1,2)的元素来替换
console.log(arr) // ['b','b','c','d','e']
// end省略的情况
// 此时arr为['b','b','c','d','e']
arr.copyWithin(1,2) // 代表要被替换的目标起始位置是1 截取索引值[2,arr.length)的元素来替换
console.log(arr) // ['b','c','d','e','e']
// 测试copyWithin()不会改变数组长度
arr.copyWithin(4,0,4) // 此时选中的截取片段是整个数组,并且要被替换的位置是从最后一位开始的
console.log(arr) // ['b','c','d','e','b']
片段赋值 fill()
fill()也是es6新增的数组方法。它的功能是可以将数组中任意片段内的所有元素全部赋值为X
fill()方法的语法为fill(value,start[,end])
value表示要赋的值。
start和end同copyWithin()方法,表示要选中的片段
举个例子
js
var arr = [1,2,3,4,5]
arr.fill(7,0,1)
console.log(arr) // [7, 2, 3, 4, 5]
arr.fill(6,0)
console.log(arr) // [6, 6, 6, 6, 6]
那些不会改变原数组的方法
连接 concat()
concat()可以将原数组和一些值或者数组拼接起来,返回值为拼接后的数组,同时不改变原数组。
concat()方法的语法为arr.concat(arr1,arr2,arr3)
如果无任何参数,则会返回一个原数组的浅拷贝数组(相当于原数组拼了个空气,所以就还是原数组)
举例
js
var arr = [1,2,3,4,5]
arr.concat([3,4,5]) // 返回值为[1,2,3,4,5,3,4,5]
console.log(arr) // [1,2,3,4,5] 原数组不会被改变
截取 slice()
slice()会截取一部分数组片段形成一个新数组,原数组并不会被改变。 语法为slice([start[, end]])
start和end就代表要截取片段的位置。同样是左闭右开区间 [start,end)
要注意的是如果start是个负数,那么就代表从length+start 的位置开始截取,那如果此时end如果为负数也只能是比start更大(end>start,不是绝对值比较)的负数,如果是正数则其值也得大于start+length否则就返回空数组。总结来说就是返回[start选中的值,end选中的值)之间的元素
如果start和end都省略,就返回一个原数组的浅拷贝数组。
举例
js
const arr = [1,2,3,4,5]
arr.slice(1,3) // [2,3] // 正常截取
arr.slice() // [1,2,3,4,5] // start和end省略,返回数组浅拷贝
arr.slice(-1,-2) // [] // start是负数,end比start还小 返回空数组
arr.slice(-2,-1) // [4]
在开发的时候我见过有人把slice和splice记混的,大伙注意一个是截取一个是替换
转字符串 join()、toString() 和 toLocaleString()
join()方法可以把数组内所有元素拼接成一个字符串
语法为arr.join([separator = ',']) separator代表连接符,也就是你希望用什么来把数组内每个元素连接到一起,缺省时则默认为逗号(,)。
举个例子
js
var arr = ['一','起','努','力','提','升']
arr.join() // '一,起,努,力,提,升'
arr.join(' ') // '一 起 努 力 提 升'
arr.join('|') // '一|起|努|力|提|升'
arr.join('') // '一起努力提升'
既然join()可以把数组拼成字符串,那toString()是做什么呢?
toString()的原理就是 将数组内每个元素的toString()返回值形成的数组调用join()来形成字符串。(当然只能是由逗号隔开)
所以对于一个数组而言,它的toString()和join()的明显区别就在于join()可以自由控制连接符,而toString()只能用逗号。
说完了toString(),那toLocaleString()是做什么的呢?
toLocaleString()的用法和原理在数组这里几乎与toString()一致,只不过区别在于toLocaleString()返回这个数字特定于语言环境的表示字符串,在没有指定区域的基本使用时,返回使用默认的语言环境和默认选项格式化的字符串。
查索引 indexOf() 和 lastIndexOf()
indexOf()和lastIndexOf()是一对功能相似的方法。
区别在于
indexOf() 方法用于查找元素在数组中第一次出现时的索引,如果没有,则返回-1。
而lastIndexOf()方法用于查找元素在数组中最后一次出现时的索引,如果没有,则返回-1。也就是说 lastIndexOf()是indexOf()的逆向查找。
鉴于它们的相似度非常高,接下来就以indexOf()为例进行说明。
indexOf()语法 :arr.indexOf(element, fromIndex=0)
其中element代表要查找的元素值,fromIndex代表要开始查找的位置,默认为0。(lastIndexOf()默认为length-1)
查找过程使用"===" 即严格等于。
举例
js
var arr = [1,2,3,4,5]
arr.indexOf(3) // 2
arr.lastIndexOf(3) // 2
// 如果arr.indexOf('x') === arr.lastIndexOf('x') 则说明该数组中仅有一个'x'
是否存在 includes()
includes()用来判断当前数组是否包含某个指定的值,如果是,则返回 true,否则返回 false。
它的语法和用法几乎与indexOf()相同,可能你会想:"那为什么不用indexOf('x')===-1 来判断数组里是否有这个元素呢? "
答案是includes()可以判断NAN 而indexOf()不能。
举例
js
var arr = [1,2,3,4,5]
arr.includes(3) // true
arr.includes(666) // false
那些可以遍历数组的方法
forEach()
forEach()方法可以让数组的每项元素都执行一次函数,其返回值为undefined。
语法:arr.forEach(fn, thisArg)
其中 fn就代表要执行的函数, thisArg为可选项,表示fn函数的this对象。
fn函数可以接受三个参数, 当前正在被处理的元素的值value、当前元素的数组索引index、数组本身array。
举个例子
js
var arr = [1,2,3,4,5]
var result = arr.forEach(function(value,index,array){
array[index] = value*index
})
console.log(arr) // [0, 2, 6, 12, 20]
console.log(result) // undefined
every() 和 some()
every() 方法使用传入的函数测试所有元素:
只要其中有一个函数返回值为 false,那么该方法的返回值为 false;
只有全部返回 true,那么该方法的返回值才为 true。
而some()则和every()相反:
只要其中有一个函数返回值为 true,那么该方法的返回值就为 true;
只有全部返回 false,那么该方法的返回值才为 false。
简单理解: every就是"&&" ,some就是"||"
reduce() 和 reduceRight()
reduce() 方法对数组中的每个元素按序执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
第一次执行回调函数时,不存在"上一次的计算结果"。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被用作初始值,迭代器将从第二个元素开始执行(即从索引为 1 而不是 0 的位置开始)
语法:arr.reduce(fn(previousValue,value,index,array), initialValue)
fn为回调函数,有四个参数:
- previousValue 上一次调用回调返回的值,或者是提供的初始值
- value 数组中当前被处理元素的值
- index 当前元素在数组中的索引
- array 数组自身
initialValue为可选项,用于指定第一次调用 fn 的第一个参数。
一个很常见的例子就是使用reduce()的特性来完成累加器(这个例子太经典以至于有一次面试的时候面试官问我reduce,我脑子一热竟然只能记起例子)
举个累加器用法的例子:
js
var arr = [1,2,3,4,5]
arr.reduce((cur,next) => cur+next, 0) // 15
而reduceRight()的区别在于reduceRight()是从右到左按序执行,除了和reduce()方向相反外其他完全一致。
filter
filter()方法可以用传入的函数对数组的每一个元素进行过滤,滤掉那些不满足条件的元素,返回一个由所有通过过滤的元素组成的数组。
其语法为arr.filter(fn, thisArg)
这里的参数同forEach()
举个例子:
js
var arr = [1,2,3,4,5]
arr.filter( item => item > 3 ) // [4, 5]
map
map() 方法遍历数组,使用传入函数处理每个元素,并返回函数的返回值组成的新数组。
它的用法语法参数都可以参考forEach()
js
var arr = [1,2,3,4,5]
arr.map( item => item * 3 ) // [3, 6, 9, 12, 15]
find 和 findIndex
find() 方法和findIndex()都是es6提出的数组方法。
find()返回数组中第一个满足条件的元素(如果有的话), 如果没有,则返回undefined。
findIndex()返回数组中第一个满足条件的元素的索引(如果有的话)否则返回-1。
它的用法语法参数都可以参考forEach()
entries()、keys() 和 values()
entries()、keys()和values()都是es6提出的数组方法。
entries()返回一个数组迭代器对象,该对象包含数组中每个索引的键值对。
keys()返回一个数组索引的迭代器。
values()返回一个数组迭代器对象,该对象包含数组中每个索引的值。
结语
至此,JS中数组的常见方法就已经介绍完了。
这是前端菜鸟提升之路系列的第一篇文章,也是我第一次在论坛上发布文章,欢迎大家留言讨论,不足之处也烦请批评指正。