本文是笔者阅读某数组去重文章后的我的笔记,每个方法都有动手测试。
文章连接:
juejin.im/post/5b0284… github.com/mqyqingfeng…javascript
测试代码以下:java
var arr = [];
// 生成[0, 100000]之间的随机数
for (let i = 0; i < 100000; i++) {
arr.push(0 + Math.floor((100000 - 0 + 1) * Math.random()))
}
console.time('测试时长:');
arr.unique();
console.timeEnd('测试时长:');
复制代码
注:每种方法都是对同一已去重的数组arr进行测试。git
没有什么算法是for循环解决不了的,一个不够,就再嵌套几个o.o....github
Array.prototype.unique = function(){
let newArr = [];
let arrLen = this.length;
for(let i = 0; i < arrLen; i++){
let bRepeat = true;
for(let j = 0;j < arrLen; j++){
if(this[i] === newArr[j]){
flg = false;
break;
}
}
if(bRepeat){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
Array.prototype.unique = function(){
let newArr = [];
for(let i = 0, arrLen = this.length; i < arrLen; i++){
let bRepeat = true;
for(let j = 0, resLen = newArr.length; j < resLen; j++){
if(this[i] === newArr[j]){
flg = false;
break;
}
}
if(bRepeat){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
Array.prototype.unique = function(){
let newArr = [];
for(let i = 0, arrLen = this.length; i < arrLen; i++){
for(var j = 0, resLen = newArr.length; j < resLen; j++){
if(this[i] === newArr[j]){
break;
}
}
//若是新数组长度与j值相等,说明循环走完,没有重复的此元素
if(j === resLen){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
双层循环时长:算法
写法1:28014.736083984375ms
写法2:3643.562255859375ms
写法3:2853.471923828125ms
复制代码
优势: 兼容性好,简单易懂
扩展思考:有效的减小循环次数及临时变量的产生可提高代码执行效率。数组
ES6给数组原型添加indexOf()方法,返回在该数组中第一个找到的元素位置,若是它不存在则返回-1浏览器
Array.prototype.unique = function(){
let newArr = [];
for(let i = 0, arrLen = this.length; i < arrLen; i++){
if(this.indexOf(this[i]) === -1){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
filter()方法使用指定的函数测试全部元素,并建立一个包含全部经过测试的元素的新数组。经过数组的过滤效果,拿每个元素的索引与indexOf(当前元素)返回的值比较。数据结构
Array.prototype.unique = function(){
let newArr = this.filter((item, index) => {
return this.indexOf(item) === index;
});
return newArr;
}
复制代码
Array.prototype.unique = function(){
let newArr = [];
this.forEach(item => {
if(this.indexOf(item) === -1){
newArr.push(item);
}
});
return newArr;
}
复制代码
ES6新增for-of循环,比for-in循环,forEach更强大好用dom
Array.prototype.unique = function(){
let newArr = [];
for(let item of this){
if(this.indexOf(item) === -1){
newArr.push(item);
}
}
return newArr;
}
复制代码
测试:函数
方法1: 4826.351318359375ms
方法2: 4831.322265625ms
方法3: 4717.027099609375ms
方法4: 4776.078857421875ms
复制代码
扩展思考:一层循环的效果差很少。关于遍历,建议用for-of。
Array.prototype.unique = function(){
let newArr = [];
for(let item of this){
if(!newArr.includes(item)){
newArr.push(item);
}
}
return newArr;
}
复制代码
测试:3700.76220703125ms
结论:相比较indexOf更快!且代码更优雅
Array.prototype.unique = function(){
let newArr = [];
this.sort();
for(let i = 0, arrLen = this.length; i < arrLen; i++){
if(this[i] !== this[i+1]){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
Array.prototype.unique = function(){
let newArr = [];
this.sort();
for(let i = 0, arrLen = this.length; i < arrLen; i++){
if(this[i] !== newArr[newArr.length - 1]){
newArr.push(this[i]);
}
}
return newArr;
}
复制代码
测试:
100.182861328125ms
89.683837890625ms
复制代码
扩展思考:
有序数组的遍历速度要比无序数组快好多倍!!!并且不仅是javascript语言,其余语言也这样,好比java,Python!!若是实战中存在大数组遍历,建议可先排序!
复制代码
结合以上分析再优化:
Array.prototype.unique = function(){
//加concat(),防止污染原数组,固然,仅针对一维数组
return this.concat().sort().filter((item, index) =>{
return item !== this[index+1];
});
}
复制代码
测试:
89.2060546875ms
复制代码
reduce() 方法接收一个函数做为累加器,数组中的每一个值(从左到右)开始缩减,最终计算为一个值。
Array.prototype.unique = function(){
//仅针对一维数组
return this.concat().sort().reduce((total, item) =>{
if(!total.includes(item)){
total.push(item);
}
return total;
}, []);
}
复制代码
测试:
4022.2578125ms
复制代码
这个慢多了o.o....
原理是利用对象的键的惟一性。
但须要注意:
解决第1、第三点问题,实现一:
Array.prototype.unique = function () {
const newArray = [];
const tmp = {};
for (let i = 0, arrLen = this.length; i < arrLen; i++) {
if (!tmp[typeof this[i] + this[i]]) {
tmp[typeof this[i] + this[i]] = 1;
newArray.push(this[i]);
}
}
return newArray;
}
复制代码
解决第二点问题,实现二:
Array.prototype.unique = function () {
const newArray = [];
const tmp = {};
for (let i = 0, arrLen = this.length; i < arrLen; i++) {
// 使用JSON.stringify()进行序列化
if (!tmp[typeof this[i] + JSON.stringify(this[i])]) {
// 将对象序列化以后做为key来使用
tmp[typeof this[i] + JSON.stringify(this[i])] = 1;
newArray.push(this[i]);
}
}
return newArray;
}
复制代码
优化:
// 使用 JSON.stringfiy 处理
Array.prototype.unique = function () {
return this.filter((item, index) => {
return this.findIndex(element => {
return JSON.stringfy(item) === JSON.stringfy(element)
}) === index;
});
}
}
复制代码
测试:
实现一:104.859130859375ms
实现二:120.89697265625ms
复制代码
ES6新增了Set和Map 数据结构,性质与java相似。如set对象相似数组,成员都是不重复的。
Array.from()+Set()
from用于将类数组对象转为真正的数组,是数组的静态方法
Array.prototype.unique = function(){
return Array.from(new Set(this));
}
复制代码
测试:
20.884033203125ms
复制代码
利用扩展运算符在简化:
Array.prototype.unique = function(){
return [...new Set(this)];
}
复制代码
测试:
16.0419921875ms
复制代码
是否是速度更快了??
Map
方法1:
Array.prototype.unique = function () {
let newArray = [];
let map = new Map();
for (let item of this) {
if (!map.has(item)) {
map.set(item, 1);
newArray.push(item);
}
}
return newArray;
}
复制代码
方法2:更优雅的写法
Array.prototype.unique = function () {
let map = new Map();
return this.filter(item => {
return map.has(item) || map.set(item, 1);
});
}
复制代码
测试:
方法1: 20.84130859375ms
方法2: 16.893798828125ms
复制代码
结合ES6的一些新特性,数组去重速率能够提升上百倍!代码的简介性和优雅度也大大提升! 虽然数组去重有不少种方法,写法不一样,速度不一样,但并非最快的就是最好的,适合场景才是第一要求。 好比如下数组:
let arr = [1, '1', { name: 1, age: 12 }, { name: 1, age: 12 }, , , {},{}, undefined,undefined ,NaN,NaN,null,null, [],[]];
复制代码
这种数组就得不是上面每一种方法都实用了。
再好比:
var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1
arr.includes(NaN) // true
复制代码
因此,选择哪一种去重方法,必须结合业务、数据元素类型以及浏览器兼容性等因素来考虑。