理解Underscore中的uniq函數

uniq函數,是Underscore中的一個數組去重函數,給它傳遞一個數組,它將會返回該數組的去重副本。javascript

1 ES6版本去重

在ES6版本中,引入了一個新的數據結構——set,這是一種相似數組的數據結構,它有個最大的特色就是內部的每個元素都是獨一無二的,因此咱們能夠利用它來對數組進行去重:java

var uniq = function(array) {
    var set = new Set(array);
    return [...set];
}

  

這是目前而言最快速簡介的數組去重方法。可是因爲瀏覽器兼容問題,目前ES6尚未徹底普及,這樣的方法可能在老舊版本的瀏覽器當中沒法起到做用。因此咱們仍是須要使用ES5來實現。git

2 ES5版本去重

對於接受的數組,咱們能夠對其進行遍歷,使用一個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'}],可是因爲連個對象引用值不相等,因此比較時,不會對這兩個對象進行去重,致使最後返回的結果是兩個都存在,這顯然不是咱們所指望的。數組

咱們須要一個指定比較規則的函數。瀏覽器

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處理後的返回值再進行比較,這樣就能夠達到對象比較的目的。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;
}));

  

最後返回的結果可以符合咱們去重的要求。

如今去重的問題解決了,能夠提升一下效率嗎?

若是咱們獲得的是一個有序的數組(不管是數組排序仍是字符串排序),咱們能夠只比較相鄰兩項是否相同來去重,這樣更加簡單快速。

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

相關文章
相關標籤/搜索