uniq函式,是Underscore中的一個陣列去重函式,給它傳遞一個陣列,它將會返回該陣列的去重副本。
1 ES6版本去重
在ES6版本中,引入了一個新的資料結構——set,這是一種類似陣列的資料結構,它有個最大的特點就是內部的每一個元素都是獨一無二的,所以我們可以利用它來對陣列進行去重:
var uniq = function(array) {
var set = new Set(array);
return [...set];
}
複製程式碼
這是目前而言最快速簡介的陣列去重方法。但是由於瀏覽器相容問題,目前ES6還沒有完全普及,這樣的方法可能在老舊版本的瀏覽器當中無法起到作用。所以我們還是需要使用ES5來實現。
2 ES5版本去重
對於接受的陣列,我們可以對其進行遍歷,使用一個result陣列存放獨一無二的元素,對於傳入陣列的每一項,在result中進行檢索,如果result中不存在,那麼就推入result中,最後返回result即可:
var uniq = function(array) {
var result = [];
var length = array.length;
var i;
for(i = 0; i < length; i++) {
if(result.indexOf(array[i]) < 0) {
result.push(array[i]);
}
}
return result;
};
複製程式碼
該函式已經能夠比較簡單的數值、字串、布林值等簡單值了,但是如果是複雜物件的話,可能就達不到去重的目的,比如:
var objArr = [{name: `a`}, {name: `a`}];
console.log(uniq(objArr));
複製程式碼
我們可能會希望返回值是[{name: `a`}],但是由於連個物件引用值不相等,所以比較時,不會對這兩個物件進行去重,導致最後返回的結果是兩個都存在,這顯然不是我們所期望的。
我們需要一個指定比較規則的函式。
3 規則定製版去重函式
我們無法預知使用者傳遞的陣列內元素的型別,所以我們最好能夠讓使用者自定義比較規則,最好的辦法就是讓使用者傳遞函式作為引數。
預設函式接受的引數即為陣列中的某一項:
var uniq = function(array, func) {
var result = [];
var length = array.length;
var i;
if(!func) {
for(i = 0; i < length; i++) {
if(result.indexOf(array[i]) < 0) {
result.push(array[i]);
}
}
}
else {
var seen = [];
for(i = 0; i < length; i++) {
if(seen.indexOf(func(array[i])) < 0) {
seen.push(func(array[i]));
result.push(array[i]);
}
}
}
return result;
};
複製程式碼
在func沒有被傳遞時,直接進行比較;如果傳遞了func函式,那麼對於array中的每一項,使用func處理後的返回值再進行比較,這樣就可以達到物件比較的目的。
再次使用物件進行實驗:
var objArr = [{id: `a`}, {id: `a`}, {id: `b`}];
console.log(uniq(objArr, function(item) {
return item.id;
}));
複製程式碼
輸出結果中只有兩個物件,說明達到了要求。
傳遞了這個自定義函式之後,去重的靈活性就大大的增加了。比如對於一個傳遞的物件陣列,其中的每個物件都包含兩個屬性——name和age,我們需要比較這些物件,只有當name和age都相同的時候,我們才認為兩個物件相同,那麼:
var persons = [{name: `dm`, age: 22}, {name: `dm`, age: 23}, {name: `dm`, age: 22}];
console.log(uniq(persons, function(item) {
return item.name + item.age;
}));
複製程式碼
最後返回的結果能夠符合我們去重的要求。
現在去重的問題解決了,可以提高一下效率嗎?
如果我們得到的是一個有序的陣列(無論是陣列排序還是字串排序),我們可以只比較相鄰兩項是否相同來去重,這樣更加簡單快速。
4 快速去重
我們可以給uniq函式新增一個引數——isSorted,代表傳遞的陣列是否是有序陣列。
var uniq = function(array, isSorted, func) {
var result = [];
var length = array.length;
var i;
var seen = [];
if(isSorted && !func) {
for(i = 0; i< length; i++) {
if(array[i] == seen) continue;
else {
result.push(array[i]);
seen = array[i];
}
}
}
else if(func){
for(i = 0; i < length; i++) {
if(seen.indexOf(func(array[i])) < 0) {
seen.push(func(array[i]));
result.push(array[i]);
}
}
}
else{
for(i = 0; i < length; i++) {
if(result.indexOf(array[i]) < 0) {
result.push(array[i]);
}
}
}
return result;
};
複製程式碼
這樣的實現就比較完善了,其中重要的點是對於seen這個變數的運用。
以上程式碼的實現思想就是來源於Underscore,只不過實現得比Underscore更加簡陋,相對而言不那麼完善。
5 Underscore實現陣列去重
以下就是Underscore的原始碼(附註釋):
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// The faster algorithm will not work with an iteratee if the iteratee
// is not a one-to-one function, so providing an iteratee will disable
// the faster algorithm.
// Aliased as `unique`.
//陣列去重函式,使得陣列中的每一項都是獨一無二的。
_.uniq = _.unique = function (array, isSorted, iteratee, context) {
//如果沒有傳遞isSorted引數(即傳遞值不是Boolean型別),那麼預設為false,其餘引數重新賦值。
if (!_.isBoolean(isSorted)) {
context = iteratee;
iteratee = isSorted;
isSorted = false;
}
//如果傳遞了iteratee,那麼使用cb方法包裝(確保返回一個函式),然後重新賦值。
if (iteratee != null) iteratee = cb(iteratee, context);
//儲存結果。
var result = [];
//用於存放array的值便於下一次比較,或者用於儲存computed值。
var seen = [];
//遍歷array陣列。
for (var i = 0, length = getLength(array); i < length; i++) {
//value表示當前項,computed表示要比較的項(有iteratee時是iteratee的返回值,無iteratee時是value自身)。
var value = array[i],
computed = iteratee ? iteratee(value, i, array) : value;
if (isSorted && !iteratee) {
//如果陣列是有序的,並且沒有傳遞iteratee,則依次比較相鄰的兩項是否相等。
//!0===true,其餘皆為false。
if (!i || seen !== computed) result.push(value);
//seen存放當前的項,以便於下一次比較。
seen = computed;
} else if (iteratee) {
//如果傳遞了iteratee,那麼seen就用於存放computed值,便於比較。
//之所以不直接使用result存放computed值是因為computed只用於比較,result存放的值必須是原來陣列中的值。
if (!_.contains(seen, computed)) {
seen.push(computed);
result.push(value);
}
} else if (!_.contains(result, value)) {
//isSorted為false並且iteratee為undefined。
//可以理解為引數陣列中是亂序數字,直接比較就好了。
result.push(value);
}
}
return result;
};
複製程式碼
陣列去重是一件說容易也容易,說簡單也簡單的事情,就看你怎麼做了。
更多Underscore原始碼解讀:GitHub