爲了比較這些不一樣解法的性能,先寫了一個測試模板,用來計算它們的耗時,測試環境是在谷歌瀏覽器。前端
// 先生成一個長度足夠長的數組
let arr1 = Array.from(Array(100000), (item, index) => index);
let arr2 = Array.from(Array(50000), (item, index) => index * 2);
let arr = [...arr1, ...arr2];
// 方法所用時長
console.log(`去重前數組的長度: ${arr.length}`);
let startTime = +new Date();
// unique方法返回去重後的數組
let result = unique(arr);
let endtTime = +new Date();
console.log(`去重後數組的長度: ${result.length}`);
console.log(`耗時:${endtTime - startTime}毫秒`);
// 測試方法
function unique(arr){
if (!Array.isArray(arr)) {
console.log('arr 不是數組');
return;
}
return unique1(arr); // 具體方法
}
複製代碼
這個是最容易理解的方法,外層循環遍歷元素,內層循環檢查是否重複。數組
定義一個數組res
保存結果,遍歷須要去重的數組,若是該元素已經存在在res
中,則說明是重複的元素;若是沒有,則放入 res
中。瀏覽器
function unique1(arr){
var res = [];
var isRepeat;
for (let ai = 1, alen = arr.length; ai < alen; ai++) {
isRepeat = false;
for (let ri = 0, rlen = res.length; ri < rlen; ri++) {
if (res[ri] == arr[ai]) {
isRepeat = true;
break;
}
}
if (!isRepeat) res.push(arr[ai]);
}
return res;
}
// 輸出
// 去重前數組的長度: 150000
// 去重後數組的長度: 100000
// 耗時:3967毫秒
// 重複試驗輸出:395四、3990、394二、397五、3964
複製代碼
嗯嗯,這個方法處理一個15w長度的數組要4秒左右。這個寫法是將原數組中的元素和結果數組中的元素一一做比較。數據結構
下面這個寫法是比較兩個原數組,而後將重複元素的最後一個元素放入結果數組中。性能
function unique1(arr) {
var res = [];
var alen = arr.length;
for (let ai = 1; ai < alen; ai++) {
for (let ri = ai + 1; ri < alen; ri++){
if (arr[ai] === arr[ri]) ri = ++ai;
}
res.push(arr[ai]);
}
return res;
}
// 耗時:931四、923二、912二、9220、9175
複製代碼
可是這個寫法處理15W長度的數組耗時是九千多毫秒。緣由是第一個寫法是將原數組arr
和結果數組res
比較,第二個寫法是將兩個原數組本身做比較(這個時間複雜度沒得說,就是O(n^2)
)。結果數組res
一開始的長度是0,和原數組比較至關於一個巨人揹着一個小小孩走路,雖然這個小小孩會慢慢長大,但老是比巨人要小不少,天然會走快些。第二個寫法是原數組跟原數組比較,至關於一個巨人揹着另外一個一樣重的巨人走路,這樣天然也走不快。測試
因此用雙重for
循環比較時,不要比較兩個原數組,要將原數組和結果數組比較。ui
另外,不要使用splice去除原數組重複的這種方法,這種更加耗時。它不只是要作`len--`和`i--`的操做,這個方法自己是這樣的,你每在中間移除一個值,後面的所有就要往前面移一個位置,這無疑是很耗時的。因此,在數據量大的時候是很是很是不建議用這種方法去去重。 spa
在實驗循環方法:for
循環、for...of
、filter()
、map()
、forEach()
、reduce()
等方法分別搭配indexOf()
時,獲得的結果都相差不大,結構也都差很少,都是一次循環加indexOf()
。 因此便將它們歸爲一類。code
function unique2(arr){
var res = arr.length > 0 ? [arr[0]] : [];
for (let ai = 1, alen = arr.length; ai < alen; ai++) {
if (res.indexOf(arr[ai]) === -1) res.push(arr[ai]);
}
return res;
}
// 耗時:776一、773五、784五、777五、7828
複製代碼
function unique2(arr) {
var res = [];
for (let item of arr) {
if (res.indexOf(item) === -1) res.push(item);
}
return res;
}
// 耗時:780五、777一、783五、784一、7787
複製代碼
function unique2(arr) {
var res = arr.filter((item, index)=> {
return arr.indexOf(item) === index;
})
return res;
}
// 耗時:783三、780九、7820、774九、7778
複製代碼
function unique2(arr) {
var res = arr.map((item, index)=> {
return arr.indexOf(item) === index;
})
return res;
}
// 耗時:777三、784二、788四、781五、776三、7855
複製代碼
function unique2(arr) {
var res = [];
arr.forEach((item, index, array) => {
if (res.indexOf(item) == -1) res.push(item);
})
return res;
}
// 耗時:780五、778九、773七、777一、7780
複製代碼
也能夠是循環方法搭配includes()
使用,includes()
方法是用來判斷一個數組是否包含一個指定的值,若是是返回 true,不然false。includes()
和indexOf()
相似,耗時也都差很少。對象
function unique2(arr) {
var res = [];
for (let item of arr) {
if (res.includes(item) === false) res.push(item);
}
return res;
}
// 耗時:778五、786九、792五、787二、7972
複製代碼
啊啊!!!這種一次循環加一個indexOf()
或者includes()
(還有一個lastIndexOf ()
,誰想試的話就本身試哈,再見!!!)的方法終於試完了,之後誰跟我說這種方法好用我跟他急,簡潔是簡潔了,可是性能真的是不敢恭維(時間複雜度是O(n^2)
)。雙重for
循環寫好了性能都比這個方法要好!!!
先使用 sort()對數組進行排序,而後比較相鄰元素是否相等,從而排除重複項
function unique3(arr) {
arr = arr.sort();
var res = arr.length > 0 ? [arr[0]] : [];
for (let ai = 1, alen = arr.length; ai < alen; ai++) {
if (arr[ai] !== arr[ai-1]) res.push(arr[ai]);
}
return res;
}
// 耗時:1三、1三、1四、1三、12
複製代碼
喵喵喵!!我沒有看錯吧!這種方法只要13毫秒?只走了一次排序和一次循環,這個方法的時間複雜度是O(n),嗯嗯,效率果真高不少。
來來,把數組長度乘以10,咱們來看看要多長時間。
// 耗時:10七、11六、130、11四、120
複製代碼
把數組長度乘以10之後,耗時也差很少是乘以10。不過仍是很快嘛!
ES6
新增了Set
這一數據結構,相似於數組,Set
的成員具備惟一性,基於這一特性,就很是適合用來作數組去重。
function unique4(arr) {
return Array.from(new Set(arr)); // 耗時:1四、1三、十一、十、11
// return [...new Set(arr)]; // 耗時:1五、1四、十一、十、10
}
複製代碼
關於Set
的使用,還能夠有兩種搭配,一個是Array.from()
,一個是...(展開運算符)
,耗時都差很少。那一種均可以。
來,乘10。(「你的月薪多少?」,"3w。","請你誠實!",「我乘10了呀!」)
// 耗時:22一、25九、22三、22四、221
// 耗時:20九、21六、21五、22五、227
複製代碼
150w長度的數組,性能仍是OK的。
ES6
新增的還有Map()
,這種方法和Set()
相似,也是具備惟一性。
function unique5(arr) {
var res = [];
var myMap = new Map();
for (var ai = 0, alen = arr.length; ai < alen; ai++) {
if(!myMap.has(arr[ai])){
myMap.set(arr[ai], 1);
res.push(arr[ai]);
}
}
return res;
}
// 耗時: 1九、2一、20、1八、21
複製代碼
這個就不乘10了,這種類型的數組去重用Set()
就好,代碼簡潔又快捷。
利用對象的屬性不會重複這一特性,校驗數組元素是否重複。
function unique6(arr){
var obj = {};
var res = [];
for (let item of arr) {
if (!obj[item]) {
res.push(item);
obj[item] = 1;
}
}
return res;
}
// 耗時: 十一、1七、1五、十、10
複製代碼
這種法子也是能夠的嘛,可是由於對象的Key
是字符串,仍是有一些限制的,可是在這裏咱們就不討論了,這裏測試比較的是性能。
在這裏我要爲雙重for
循環正名,它並無想象中的那麼耗性能,關鍵在於怎麼寫,並且這種方法是兼容性最好的,哈哈。不過在ES6
新出的語法面前,它依然是個弟弟。對於這幾種去重的方法,我最喜歡的就是Set()
了,真是好用。我大前端真是發展得愈來愈快愈來愈好了,哈哈,你們一塊兒加油吧!
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。