uniq函數,是Underscore中的一個數組去重函數,給它傳遞一個數組,它將會返回該數組的去重副本。javascript
在ES6版本中,引入了一個新的數據結構——set,這是一種相似數組的數據結構,它有個最大的特色就是內部的每個元素都是獨一無二的,因此咱們能夠利用它來對數組進行去重:java
var uniq = function(array) { var set = new Set(array); return [...set]; }
這是目前而言最快速簡介的數組去重方法。可是因爲瀏覽器兼容問題,目前ES6尚未徹底普及,這樣的方法可能在老舊版本的瀏覽器當中沒法起到做用。因此咱們仍是須要使用ES5來實現。git
對於接受的數組,咱們能夠對其進行遍歷,使用一個result數組存放獨一無二的元素,對於傳入數組的每一項,在result中進行檢索,若是result中不存在,那麼就推入result中,最後返回result便可:es6
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; };
該函數已經可以比較簡單的數值、字符串、布爾值等簡單值了,可是若是是複雜對象的話,可能就達不到去重的目的,好比:github
var objArr = [{name: 'a'}, {name: 'a'}]; console.log(uniq(objArr));
咱們可能會但願返回值是[{name: 'a'}],可是因爲連個對象引用值不相等,因此比較時,不會對這兩個對象進行去重,致使最後返回的結果是兩個都存在,這顯然不是咱們所指望的。數組
咱們須要一個指定比較規則的函數。瀏覽器
咱們沒法預知用戶傳遞的數組內元素的類型,因此咱們最好可以讓用戶自定義比較規則,最好的辦法就是讓用戶傳遞函數做爲參數。數據結構
默認函數接受的參數即爲數組中的某一項:函數
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處理後的返回值再進行比較,這樣就能夠達到對象比較的目的。es5
再次使用對象進行實驗:
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; }));
最後返回的結果可以符合咱們去重的要求。
如今去重的問題解決了,能夠提升一下效率嗎?
若是咱們獲得的是一個有序的數組(不管是數組排序仍是字符串排序),咱們能夠只比較相鄰兩項是否相同來去重,這樣更加簡單快速。
咱們能夠給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更加簡陋,相對而言不那麼完善。
如下就是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