原文地址: https://juejin.cn/post/6901621560444977160#heading-1
(手机端可能看不清)获取高清PDF,请在微信公众号【小狮子前端Vue】回复【数组方法】
开门见山,我先介绍一下本文整体目录结构,介绍我将输出的大概内容。
显然,数组的方法有很多很多,但是实际工作或者面试过程中有些用到的少之又少,因此,我不会将所有的方法都提出来,然后解析。那么,我会将重要程度比较高的方法尽量用详细简洁的语言带过,同时例举几个样例,自测案例为你加深巩固。
其次,本文还会提及面试常考问题,比如经典扁平化问题,数组去重、合并、排序、改变原数组的方法、不改变原数组的方法等等。
好了,让我们进入正文~
犹记得今年快手面试官就问了一道经典面试题:说说你所知道的数组的遍历方法?
我想这个问题小伙伴们面试时应该也会遇到过吧,面试官问这个问题其一就是考察你对数组方法掌握程度,其二可以通过你说的方法选择其中一个继续深度询问,比如 reduce
、map
使用等等,因此,我将 数组遍历 作为第一个模块来讲解。
let arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// 1
// 2
// 3
// 4
// 5
复制代码
摘自MDN
上一句话,for...of
语句遍历可迭代对象定义要迭代的数据。简单来说,for...of
遍历的就是 value
。
let arr = ['Chocolate', 'zhlll', 'lionkk'];
for (let val of arr) {
console.log(val);
}
// Chocolate
// zhlll
// lionkk
复制代码
摘自MDN
上一句话,for...in
语句以任意顺序迭代对象的可枚举属性。简单来说,for...in
遍历的就是 key
。对于数组,key
对应着的是数组的下标索引。
let arr = ['Chocolate', 'zhlll', 'lionkk'];
for (let key in arr) {
console.log(key);
}
// 0
// 1
// 2
复制代码
先来介绍语法:
array.forEach(callback(currentValue, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
currentValue 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) forEach() 方法正在操作的数组 thisArg 可选参数,当执行回调函数callback,用作this值
简单例子:
let arr = ['Chocolate', 'zhlll', 'lionkk'];
arr.forEach(function (cur, index, arr) {
console.log(cur, index, arr);
})
// Chocolate 0 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
// zhlll 1 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
// lionkk 2 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
复制代码
从上述例子中,了解到 forEach
需要传递一个回调函数,而那三个参数,后面两个是可选的,那么如何让代码更加优雅美观一点呢,同时,后面两个参数按需添加即可:
let arr = ['Chocolate', 'zhlll', 'lionkk'];
arr.forEach((cur) => {
console.log(cur);
})
// Chocolate
// zhlll
// lionkk
复制代码
疑难点,我想小伙伴们,应该对最后一个 thisArg
有疑问吧,现在就来解释一下:
function Foo() {
this.sum = 0;
this.cnt = 0;
}
// 在原型上添加一个名为 doSth 方法
Foo.prototype.doSth = function (arr) {
arr.forEach(function (cur) {
this.sum += cur;
this.cnt++;
}, this) // this 指向实例对象
}
let foo = new Foo();
let arr = [1, 2, 3];
foo.doSth(arr);
console.log(foo.sum, foo.cnt);
// 6 3
// 解释: 6 === (1+2+3) 3 === (1+1+1)
复制代码
注意:如果使用
箭头函数表达式
来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。
因此,如果对于普通函数的话,可以看做是将 this
通过传参的形式解决无法继承 问题,当然,通过箭头函数的方式是一个不错的选择!
定义:返回一个新数组,其结果是该数组中的每个元素是调用一次提供的回调函数后的返回值。
先来介绍语法:
let newArray = array.map(function(currentValue, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
currentValue 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) map() 方法正在操作的数组 thisArg 可选参数,当执行回调函数callback,用作this值
简单例子:
let arr = ['Chocolate', 'zhlll', 'lionkk'];
let newArr = arr.map(function (cur, index, arr) {
console.log(cur, index, arr);
return cur + index;
})
// Chocolate 0 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
// zhlll 1 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
// lionkk 2 [ ‘Chocolate’, ‘zhlll’, ‘lionkk’ ]
console.log(newArr)
// [ ‘Chocolate0’, ‘zhlll1’, ‘lionkk2’ ]
复制代码
疑难点,我想小伙伴们,有了前置问题了,这次理解 thisArg
应该没有太多问题了吧,看看下面例子:
function Foo() {
this.sum = 0;
this.cnt = 0;
}
// 在原型上添加一个名为 doSth 方法
Foo.prototype.doSth = function (arr) {
let newArr = arr.map(function (cur) {
this.sum += cur;
this.cnt++;
return cur + 10;
}, this) // this 指向实例对象
return newArr;
}
let foo = new Foo();
let arr = [1, 2, 3];
console.log(foo.doSth(arr)); // [ 11, 12, 13 ]
console.log(foo.sum);// 6
console.log(foo.cnt);// 3
复制代码
一些小操作~
let arr = [1, 4, 9, 16];
let res = arr.map(Math.sqrt); // 传入Math中sqrt得到数组中每个元素的平方根
console.log(res); // [ 1, 2, 3, 4 ]
复制代码
总结:
map
不修改调用它的原数组本身(当然可以在 callback
执行时改变原数组)回调函数不返回值时,最后新数组的每个值都为undefined
this
的值最终相对于 callback
函数的可观察性是依据this规则,也就是 this
指向问题map
会返回一个新数组定义:对数组中的每个元素执行一个由您提供的 reducer
函数(升序执行),将其结果汇总为单个返回值。
先来介绍语法:
let res= array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至4个参数
accumulator 累计器 currentValue 当前值 currentIndex 当前索引 array 数组 initialValue 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。
简单例子:
let arr = [3, 5, 7, 1, 2];
let res = arr.reduce(function (acc, cur, index, arr) {
console.log(acc, cur, index, arr);
return acc + cur;
}, 0)
// 0 3 0 [ 3, 5, 7, 1, 2 ]
// 3 5 1 [ 3, 5, 7, 1, 2 ]
// 8 7 2 [ 3, 5, 7, 1, 2 ]
// 15 1 3 [ 3, 5, 7, 1, 2 ]
// 16 2 4 [ 3, 5, 7, 1, 2 ]
console.log(res);
// 18
复制代码
看完上面代码,你可能还是蒙的,怎么一下输出这么多,结合下面 gif
动图再来理解一下吧:
疑难点,我想小伙伴们对于参数那么多应该一下给看懵了,下面用一些小操作展示一下,并且提供一点自测题目加深巩固~
let arr = [1, 2, 3, 4, 5];
let res = arr.reduce((acc, cur) => {
return acc + cur;
}, 0)
console.log(res);// 15
复制代码
自测题:看看下面输出什么?
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
复制代码
1
2
and 3
3
and 6
4
1
2
and 2
3
and 3
4
1
undefined
and 2
undefined
and 3
undefined
and 4
undefined
1
2
and undefined
3
and undefined
4
reducer
函数接收4个参数:
reducer
函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。
reducer
函数还有一个可选参数initialValue
, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供initialValue
,则将使用数组中的第一个元素。
在上述例子,reduce
方法接收的第一个参数(Accumulator)是x
, 第二个参数(Current Value)是y
。
在第一次调用时,累加器x
为1
,当前值“y”
为2
,打印出累加器和当前值:1
和2
。
例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回undefined
。 在下一次调用时,累加器为undefined
,当前值为“3”, 因此undefined
和3
被打印出。
在第四次调用时,回调函数依然没有返回值。 累加器再次为 undefined
,当前值为“4”。 undefined
和4
被打印出。
总结:
initialValue
,会抛出TypeError
。initialValue
,reduce
会从索引1的地方开始执行 callback
方法,跳过第一个索引。如果提供 initialValue
,从索引0开始。acc
为传入函数的返回值,如果是 console.log
,则返回默认值 undefined
定义:创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
先来介绍语法:
let newArray = array.filter(function(currentValue, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
currentValue 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) filter() 方法正在操作的数组 thisArg(可选参数),当执行回调函数callback,用作this值
简单例子:
let arr = [1, 2, 3, 4, 5];
let newArr = arr.filter(function (cur, index) {
console.log(cur, index);
return cur % 2 == 0;
})
// 1 0
// 2 1
// 3 2
// 4 3
// 5 4
console.log(newArr); // [ 2, 4 ]
复制代码
关于 thisArg
相关可以参考上文 array.forEach() 方法
部分。
简单来说,就是返回满足条件的结果。
定义:测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回的是一个 Boolean
类型的值。
先来介绍语法:
array.every(function(currentValue, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
currentValue 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) every() 方法正在操作的数组 thisArg 可选参数,当执行回调函数callback,用作this值
let res1 = [1, 2, 3, 4, 5].every(function (cur) {
return cur > 10;
})
console.log(res1); // false
let res2 = [1, 2, 3, 4, 5].every(function (cur) {
return cur >= 1;
})
console.log(res2); // true
复制代码
关于 thisArg
相关可以参考上文 array.forEach() 方法
部分。
简单来说,就是返回是否都能满足特定条件的结果,用布尔值返回。
定义:测试数组中是不是至少有1个元素通过了被提供的函数测试,它返回的是一个 Boolean
类型的值
先来介绍语法:
array.some(function(currentValue, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
currentValue 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) some() 方法正在操作的数组 thisArg (可选参数),当执行回调函数callback,用作this值
let res1 = [1, 2, 3, 4, 5].some(function (cur) {
return cur > 10;
})
console.log(res1); // false
let res2 = [1, 2, 3, 4, 5].some(function (cur) {
return cur === 1;
})
console.log(res2); // true
复制代码
关于 thisArg
相关可以参考上文 array.forEach() 方法
部分。
简单来说,就是返回是否至少有1个满足特定条件的结果,用布尔值返回。
该方法在ECMAScript 6规范中被加入,可能不存在于某些实现中。
定义:
find
: 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
。
findIndex
:数组中通过提供测试函数的第一个元素的索引。否则,返回 -1
。
先来介绍语法:
let ele = array.find(function(elemnet, index, arr), thisArg)
let eleIndex = array.findIndex(function(elemnet, index, arr), thisArg)
复制代码
callback:为数组中每个元素执行的函数,该函数接收一至三个参数
elemnet 数组中正在处理的当前元素 index (可选) 数组中正在处理的当前元素的索引 arr (可选) find方法正在操作的数组 thisArg 可选参数,当执行回调函数callback,用作this值
let res1 = [1, 2, 3, 4, 5].find(function (cur) {
return cur > 2;
})
console.log(res1); // 3
let res2 = [1, 2, 3, 4, 5].findIndex(function (cur) {
return cur > 2;
})
console.log(res2); // 2
复制代码
定义:
keys()
方法返回一个包含数组中每个索引键的 Array Iterator 对象。values()
方法返回一个新的 Array Iterator
对象,该对象包含数组每个索引的值entries()
方法返回一个新的 Array Iterator
对象,该对象包含数组中每个索引的键/值对。arr.keys()
arr.values()
arr.entries()
复制代码
简单例子:
let arr = ['Chocolate', 'zhlll', 'lionkk'];
let itKeys = arr.keys();
let itVals = arr.values();
let itEntries = arr.entries();
for (let it of itKeys) {
console.log(it);
}
// 0
// 1
// 2
for (let it of itVals) {
console.log(it);
}
// Chocolate
// zhlll
// lionkk
for (let it of itEntries) {
console.log(it);
}
// [ 0, ‘Chocolate’ ]
// [ 1, ‘zhlll’ ]
// [ 2, ‘lionkk’ ]
复制代码
好了,到此关于数组的遍历方式基本上介绍完毕了,也许还有其它方法,但是万变不离其宗,接下来我们将探究 改变原数组 的方法。
看过我之前关于 Vue
数据劫持源码分析那篇博客文章小伙伴应该知道,里面就有提到了用装饰者模式解决无法处理数组问题。其中就有提到对于改变原始数组的方法,这些需要继续递归观察。那么,接下来,我们就来分别探讨一下它们的使用:
至于为什么我会排第一个,对,面试遇到过,当时我说对某手面试官说默认按照从小到大进行排序,通过学习后,我发现不是的...
先来介绍语法:
arr.sort([compareFunction])
复制代码
compareFunction 可选,用来指定按某种顺序进行排列的函数。 如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
否则,如果指明了compareFunction: 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前; 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
简单例子:
let arr = [1, 10, 2, 5, 8, 3];
arr.sort(); // 默认
console.log(arr); // [ 1, 10, 2, 3, 5, 8 ]
arr.sort((a, b) => a - b); // 从小到大排序
console.log(arr); // [ 1, 2, 3, 5, 8, 10 ]
arr.sort((a, b) => b - a); // 从大到小排序
console.log(arr); // [ 10, 8, 5, 3, 2, 1 ]
复制代码
类似栈、队列的一些操作
注意,push()
成功之后会返回数组的长度。
let arr = [1,2];
let res = arr.push(100);
console.log(arr); // [ 1, 2, 100 ]
console.log(res); // 3
复制代码
类似栈、队列的一些操作
let arr = [1, 2, 100];
let res = arr.pop();
console.log(arr); // [ 1, 2 ]
console.log(res); // 100
复制代码
类似栈、队列的一些操作
let arr = [1, 2, 100];
let res = arr.shift();
console.log(arr); // [ 2, 100 ]
console.log(res); // 1
复制代码
定义:将一个或多个元素添加到 数组的开头,并 (该方法修改原有数组)
注意:该方法会返回该数组的新长度
let arr = [1, 2, 100];
let res = arr.unshift(4, 5, 6);
console.log(arr); // [ 4, 5, 6, 1, 2, 100 ]
console.log(res); // 6
复制代码
定义:将数组中元素的位置颠倒,并返回该数组。
let arr = [1, 2, 3];
arr.reverse();
console.log(arr);// [ 3, 2, 1 ]
复制代码
这个我放最后一个也是有原因的,它比其它几个要更复杂一点,刚开始我也是花了老长时间才理解,而且原本一直与
split()
这些分不清楚。
定义:
通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
array.splice(start,deleteCount,item1,.....,itemX)z
复制代码
start: 指定修改的开始位置(从0计数)
1. 如果超出了数组的长度,则从数组末尾开始添加内容
2. 如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素,并且等价于array.length-n)
3. 如果负数的绝对值大于数组的长度,则表示开始位置为第0位
复制代码
deleteCount(可选) : 整数,表示要移除的数组元素个数
1. 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被 删除(含第 start 位)
2. 如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是 说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。
3. 如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新 元素。
复制代码
item1, item2, ...(可选)
要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。
从第2位开始插入“Chocolate”
let arr = ['one', 'two', 'three'];
arr.splice(2, 0, ‘Chocolate’);
console.log(arr);// [ ‘one’, ‘two’, ‘Chocolate’, ‘three’ ]
复制代码
从第 2 位开始删除 1 个元素,然后插入“Chocolate”
let arr = ['one', 'two', 'three'];
arr.splice(2, 1, ‘Chocolate’);
console.log(arr);// [ ‘one’, ‘two’, ‘Chocolate’ ]
复制代码
主流的还是这7个方法,对于改变原数组还有
fill()
和copyWithin()
方法,小伙伴们可以继续研究~
上文已经介绍
定义:通过在每个数组项上使用 callback
调用结果来创建一个新数组。
先来介绍语法:
Array.from(Array,callback(currentValue, index, arr))
复制代码
简单例子:
let arr = [1, 2, 3];
let newArr = Array.from(arr, function (cur) {
return cur + 10;
})
console.log(newArr);// [ 11, 12, 13 ]
复制代码
array.concat(array1[, array2, ...])
将一个或多个数组连接到原始数组。如下所示,连接两个数组:
let arrA = [1, 2, 3];
let arrB = [4, 5, 6];
let ans = arrA.concat(arrB);
console.log(ans);// [ 1, 2, 3, 4, 5, 6 ]
复制代码
let arrA = [1, 2, 3];
let arrB = [4, 5, 6];
let ans = [...arrA, ...arrB];
console.log(ans);// [ 1, 2, 3, 4, 5, 6 ]
复制代码
定义: 返回一个新的数组对象,这一对象是一个由 begin
和 end
决定的原数组的浅拷贝(包括 begin
,不包括 end
)——原始数组不会被改变。
先介绍语法:
arr.slice([begin[, end]])
复制代码
begin (可选)
end (可选)
let fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
let res = fruits.slice(1, 3);
let res1 = fruits.slice(1);
let res2 = fruits.slice(-1);
let res3 = fruits.slice(0, -1);
console.log(res); // [ 'Orange', 'Lemon' ]
console.log(res1);// [ 'Orange', 'Lemon', 'Apple', 'Mango' ]
console.log(res2);// [ 'Mango' ]
console.log(res3);// [ 'Banana', 'Orange', 'Lemon', 'Apple' ]
复制代码
定义:
将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
语法:
arr.join(separator)
复制代码
简单例子:
let arr = ['one', 'two', 'three'];
let res = arr.join('^');
let res1 = arr.join('&');
console.log(res); // onetwothree
console.log(res1); // one&two&three
复制代码
定义:
使用指定的分隔符字符串将一个 String
对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。
语法:
str.split([separator[, limit]])
复制代码
const str = 'The best Chocolate';
const words = str.split(’ ');
console.log(words); // [ ‘The’, ‘best’, ‘Chocolate’ ]
console.log(words[2]); // Chocolate
复制代码
定义:
返回一个字符串,表示指定的数组及其元素。
当一个数组被作为文本值或者进行字符串连接操作时,将会
自动调用
其toString
方法。
语法:
arr.toString()
复制代码
let arr = ['one', 'two', 'three'];
console.log(arr.toString()); // one,two,three
复制代码
定义:
按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组
返回。
语法:
var newArray = arr.flat([depth])
复制代码
参数
depth 可选 指定要提取嵌套数组的结构深度,默认值为 1。 返回值 一个包含将数组与子数组中所有元素的新数组。
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat()); // [ 0, 1, 2, 3, 4 ]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2)); // [ 0, 1, 2, [ 3, 4 ] ]
复制代码