一、ES5 中数组操作方法
详尽使用参看 MDN_Array
二、ES5+ 中数组操作方法
除了 flat 和 flatMap 方法详解,其它详解参看 ES6 数组给我们带来哪些操作便利?
flat()和flatMap()本质上就是是归纳(reduce) 与 合并(concat)的操作。
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
- flat()方法最基本的作用就是数组降维
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]
- 其次,还可以利用 flat() 方法的特性来去除数组的空项
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
具体可参看 MDN_Array.prototype.flat()
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。 这里我们拿map方法与flatMap方法做一个比较。
var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
// 只会将 flatMap 中的函数返回的数组 “压平” 一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]
具体参看 MDN_Array.prototype.flatMap()
三、数组操作方法对数组自身的影响
四、九种数组去重方法
测试数组去重时间代码示例
const arr = [];
// 生成[0, 100000]之间的随机数
for (let i = 0; i < 100000; i++) {
arr.push(0 + Math.floor((100000 - 0 + 1) * Math.random()))
}
// ...实现算法
console.time('test');
arr.unique();
console.timeEnd('test');
1、双重去重
基本思路:通过拿出一个元素和剩下的元素依次比较,如果全部不相等则证明此元素为唯一。
Array.prototype.unique = function () {
const newArray = [];
let isRepeat;
for (let i = 0; i < this.length; i++) {
isRepeat = false;
for (let j = 0; j < newArray.length; j++) {
if (this[i] === newArray[j]) {
isRepeat = true;
break;
}
}
if (!isRepeat) {
newArray.push(this[i]);
}
}
return newArray;
}
测试时间:
test: 3032.134033203125ms
缺点:复杂度太高
2、indexOf
基本思路:如果索引不是第一个索引,说明是重复值。
Array.prototype.unique = function () {
const newArray = [];
this.forEach(item => {
if (newArray.indexOf(item) === -1) {
newArray.push(item);
}
});
return newArray;
}
测试时间:
test: 6951.115966796875ms
3、sort
基本思路:先对原数组进行排序,然后再进行元素比较。
Array.prototype.unique = function () {
const newArray = [];
this.sort();
for (let i = 0; i < this.length; i++) {
if (this[i] !== this[i + 1]) {
newArray.push(this[i]);
}
}
return newArray;
}
测试时间:
test: 72.129150390625ms
4、includes
基本思路:判断新数组中是否含有已存在的项
Array.prototype.unique = function () {
const newArray = [];
this.forEach(item => {
if (!newArray.includes(item)) {
newArray.push(item);
}
});
return newArray;
}
测试时间:
test: 5378.988037109375ms
5、reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值
Array.prototype.unique = function () {
return this.sort().reduce((init, current) => {
if(init.length === 0 || init[init.length - 1] !== current){
init.push(current);
}
return init;
}, []);
}
测试时间:
test: 69.05908203125ms
更好的使用场景:对象数组中去掉重复的对象
6、对象键值对keys()
基本思路:利用了对象的key不可以重复的特性来进行去重。
Array.prototype.unique = function () {
const newArray = [];
const tmp = {};
for (let i = 0; i < this.length; i++) {
if (!tmp[typeof this[i] + this[i]]) {
tmp[typeof this[i] + this[i]] = 1;
newArray.push(this[i]);
}
}
return newArray;
}
测试时间:
test: 333.0009765625ms
更适合处理如对象这样的复杂的数据类型
7、Map 数据结构
两种针对简单数据类型的去重:
方法一:
Array.prototype.unique = function () {
const newArray = [];
const tmp = new Map();
for(let i = 0; i < this.length; i++){
if(!tmp.get(this[i])){
tmp.set(this[i], 1);
newArray.push(this[i]);
}
}
return newArray;
}
方法二:
Array.prototype.unique = function () {
const tmp = new Map();
return this.filter(item => {
return !tmp.has(item) && tmp.set(item, 1);
})
}
分别的测试时间:
test: 53.268798828125ms
test: 41.73193359375ms
8、Set 数据结构
也有两种简单的数据类型去重的:
方法一:
Array.prototype.unique = function () {
const set = new Set(this);
return Array.from(set);
}
方法二:
Array.prototype.unique = function () {
return [...new Set(this)];
}
测试时间:
test: 17.760009765625ms
test: 19.72509765625ms
上面两种方法只适用简单的数据类型去重,如果遇到一个比较复杂的对象数组,就去重失败,如下代码示例:
let arr = [
{ name: 'a', num: 1},
{ name: 'b', num: 1},
{ name: 'c', num: 1},
{ name: 'd', num: 1},
{ name: 'a', num: 1},
{ name: 'a', num: 1},
{ name: 'a', num: 1}
]
function unique (arr) {
return [...new Set(arr)]
}
console.log(unique(arr)) // 结果为原数组,有兴趣可以复制代码试一下
因为 set 数据结构认为对象永不相等,即使是两个空对象,在 set 结构内部也是不等的
如何解决呢?
这里需要借助 JSON.stringfy() 和 JSON.parse() 序列化对象:
// ES6对象数组所有属性去重,筛选每个数组项的字符
function unique(arr) {
const map = new Map()
return arr.filter( item => !map.has(JSON.stringify(item)) && map.set(JSON.stringify(item), 1))
}
function unique(arr) {
return [...new Set(arr.map(e => JSON.stringify(e)))].map(e => JSON.parse(e))
}
// 两者出来的结果
[
{ name: 'a', num: 1},
{ name: 'b', num: 1},
{ name: 'c', num: 1},
{ name: 'd', num: 1}
]
9、underscore.js 方法库
测试代码
_.uniq(arr);
测试时间
test: 3640.329833984375ms
总结
除了考虑时间复杂度外、性能之外,还要考虑数组元素的数据类型等问题权衡选择出采用哪种算法,通过上面的几个测试用例,可以看出利用 Set 和 Map 数据结构去重的效率会高一些。