本文是筆者閱讀某數組去重文章後的我的筆記,每個方法都有動手測試。
文章連接:
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
複製代碼
因此,選擇哪一種去重方法,必須結合業務、數據元素類型以及瀏覽器兼容性等因素來考慮。