JavaScript常用的数组操作方法以及ES6中数组新拓展的方法
前几天面试的时候被面试官问了这个问题,愣了一下,突然没反应过来哪些操作方法是ES5哪些是ES6的(平时用得比较少,记得不是很牢),后面在面试官的提醒下才说出了map和filter。今天就稍微总结一下在JavaScript中数组的操作方法吧。
一、ES6中数组新增的操作方法
1. 数组解构运算符(spread)
该运算符可以将一个数组变为参数序列。
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>] //如果扩展运算符后面是一个空数组,则不产生任何效果。 [...[], 1] // [1]
这个操作方法一般是使用在函数传参,当函数的参数个数较多时,可以通过数组spread运算符直接将数组解构,变为参数序列传入。
function push(array, ...items) { array.push(...items); } function add(x, y) { return x + y; } const numbers = [4, 38]; add(...numbers) // 42
这样操作的好处是可以代替以前函数的apply方法。在ES6之前,当数组需要传多个参数的时候,我们一般会使用apply来进行传参。
/* example1 */ function f(x, y, z) { // ... } // ES5写法 var args = [0, 1, 2]; f.apply(null, args); // ES6写法 f(...args) // example2 // ES5写法 Math.max.apply(null, [14, 3, 77]); // ES6写法 Math.max(...[14, 3, 77]); // 等同于Math.max(14, 3, 77);
同时,spread也可以对字符串进行结构:
console.log([...'hello']); // [ "h", "e", "l", "l", "o" ]
2. Array.from
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from还可以接受第二个参数,作用类似于数组的map
方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
3. Array.of
Array.of方法就是将一组数据转换为数组类型,相当于以前的Array()或new Array()。这个方法的主要目的,是弥补数组构造函数Array()
的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // [] Array(3) // [, , ,] Array(3, 11, 8) // [3, 11, 8] Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1
4. find和findIndex
这两个相当于以前indexOf方法,并且这find和findIndex的使用方法相同,唯一的区别是find方法若找到目标元素,则返回第一个符合条件的元素,若找不到目标元素的时候会返回undefined,而findIndex找到会返回符合条件元素的下标,找不到则会返回-1。使用方法如下:
find(function(value, index, arr){ ... }, object) [1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10 [1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
其中,第一个参数是一个回调函数,回调函数的参数分别是当前值、当前索引、原数组。第二个参数是绑定回调函数内部的this。
5. fill
如其名字,用于填充数组,并且会覆盖数组原来位置的数据。fill(value, start = 0, end = arr.length)。
new Array(3).fill(3); // [3, 3, 3] [1, , 2, , 3].fill(3); // [3, 3, 3, 3, 3] [1, 2, 3].fill(3, 1, 2); // [1, 3, 3]
6. includes
用法与indexOf类似,若目标元素存在于数组,则返回true,否则返回false。区别是[NaN].indexOf(Nan) === -1,[NaN].includes(NaN) === true
7. sort
Array.prototype.sort(callback),参数是一个回调函数,假设回调函数为 (a, b) => a-b。若返回值<0,则a会排在b前面,若返回值>0,则a会排在b后面。但这不是绝对的,不同浏览器会出现不同的结果!具体可以自己查阅一下资料,这里只以chrome为例。(可参考https://yq.aliyun.com/articles/64376)
let arr = [2, 3, 4, 1]; // 写法1 arr.sort((a, b) => a-b) // [1, 2, 3, 4] // 写法2 arr.sort((a, b) => { return a < b? -1 : 1; }) // [1, 2, 3, 4]
8. map
Array.prototype.map.call(arr, callback),callback也有三个参数,分别为当前值value,当前值索引index,以及当前数组arr。其返回值是一个数组,数组内容是原数组中每个元素调用callback后返回的数值。
let arr = [1, 2, 3, 4]; let newArr = arr.map(val => { return val + =1; }) console.log(arr); // [1, 2, 3 ,4] console.log(newArr); // [2, 3, 4, 5]
9. filter
Array.prototype.filter.call(arr, callback),参数与上面的map一样。返回值也是一个数组,但是数组内容是原数组中每个元素调用callback并且返回值为true的元素。可以理解为这是一个“过滤器”,只有满足callback的内容才能保留下来。而map是对数组中每一个元素进行“加工”,不会增加或减少数组中的元素。
let arr = [1, 2, 3, 4]; let newArr = arr.filter(val => { return val > 2; }) console.log(arr); // [1, 2, 3 ,4] console.log(newArr); // [3, 4]
总结一下,ES6对数组进行较大的更新,可以看到对数组新增了许多新的操作方法,弥补了ES5中的不足之处。当然,增加的内容不止上述这些,这里只是列举部分比较常见的,有兴趣深入了解的可以到官方文档进行查看。
二、常用的JavaScript数组操作方法
学习了ES6新增的方法,那么顺便复习一下ES6之前数组有哪些操作方法吧。要知道有哪些操作方法很简单,只需要在chrome浏览器中输入Array.prototype就可以看到了,这里列举一下我个人比较常用的几个方法吧。
1. reduce
array.reduce(function(total, currentValue, currentIndex, arr), initialValue),可以作为一个累加器使用。一般情况下用于计算数组元素的和,当然你也可以通过for或者forEach来解决,但是如果这时候你掏出一个reduce,会显得逼格更高一点。先解释一下各个参数吧
- total:必需。初始值, 或者计算结束后的返回值。
- currentValue:必需。当前元素
- currentIndex:可选。当前元素的索引
- arr:可选。当前元素所属的数组对象。
- initialValue:可选。传递给函数的初始值
参数看不懂没关系,结合代码实践一下就能很好地理解了。
不传初始值时:
有初始值时:
可以看到,当没传入初始值时,会默认将数组的第一个元素作为初始值传入。
2. push、pop、unshift、shift
把这四个放在一起是因为这四个十分相似。push和pop是在数组的最末尾进行增加、删除元素,而unshift和shift是在数组的最前端进行增加、删除元素。适用的场景非常多,比如说可以用数组来模拟实现栈和队列等等。
3. slice、splice
slice(start = 0, end = arr.length)和splice(start, deleteCount = arr.length - start, ...item)。这两个方法都可以实现对数组做一个切割的作用,比如说[1, 2, 3, 4, 5]我要把[3, 4]切割出来,那么就使用[1, 2, 3, 4].slice(2, 4),它的返回值就是[3, 4],但是原数组不会发生变化,还是[1, 2, 3, 4, 5]。
splice也可以拿来做出相似的效果,不过可以发现splice方法它的参数名字怎么跟slice不太一样,并且它有三个参数。因为splice最初的作用是拿来对数组内部一些元素进行删除并在删除的位置替换上新的元素。也就意味着splice方法是会改变原数组的。它的返回值是被删除的元素。
/* slice */ let arr = [1, 2, 3, 4, 5]; console.log(arr.slice(2, 4)); // [3, 4] console.log(arr); // [1, 2, 3, 4, 5]
/* splice */ let arr = [1, 2, 3, 4, 5]; console.log(arr.splice(2, 2)); // [3, 4] console.log(arr); // [1, 2, 5] let arr2 = [1, 2, 3, 4, 5] console.log(arr2.splice(2, 2, ...[6, 7])); // [3, 4] console.log(arr2); // [1, 2, 6, 7, 5]
4. join
可以将数组拼接成字符串,默认是使用’,‘进行拼接,也可以通过传参设置拼接符。
['h', 'e', 'l', 'l', 'o'].join(); // "h,e,l,l,o" ['h', 'e', 'l', 'l', 'o'].join(''); // "hello"
顺便拓展一下如何从字符串变为数组,可以用String.prototype.split这个方法。'hello'.split('')的返回结果为 ["h", "e", "l", "l", "o"]。
5. reverse
没什么好讲的,将数组内元素倒序排列。
6. concat
用于拼接多个数组。用于拼接的元素可以是数组,也可以是独立的数据,也可以是通过spread操作符解构出来的数据。
let arr = [1, 2]; console.log(arr.concat(...[3, 4], [5, 6], 7)); // [1, 2, 3, 4, 5, 6, 7] console.log(arr); // [1, 2]
需要注意的是,使用concat拼接之后得到的结果是存在concat的返回值内,并不会修改到原数组。
三、 会修改到原数组的操作方法
这个问题已经碰到过很多回了,目的是为了考察面试者对于这些操作方法的理解(我猜的,至于面试官是出于什么目的问的我就无从得知了)。
通过上面的分析我们可以看到,首先,对数组内部元素进行增加、删除的方法,肯定会修改到原数组,比如说push、pop、unshift、shift,还有一个是我们提到的splice,它可以在切割的位置新增元素,这里涉及到新增元素,所以也会修改到原数组。还有sort,它是直接在原数组的基础上进行排序的,所以也会修改原数组,reverse同理。另外还有一个fill,它是对数组做一个填充的效果,同样也会修改到原数组。
总结一下,能够修改原数组的操作方法有以下几个:
- push
- pop
- unshift
- shift
- splice
- sort
- reverse
- fill
当然,这是不完整的,还有一些是本篇文章没有提到的所以没有列举出来。需要记住的是,像map、filter这类的操作方法,它只是会在将修改后的数组作为返回值展示出来,并不会修改到原数组。