理解Underscore中的flatten函數

最近是在所在實習公司的第一個sprint,有個朋友又請假了,因此任務比較重,一直這麼久都沒怎麼更新了,這個週末賴了個牀,糾結了一下子決定仍是繼續寫這個系列,雖然比較乏味,可是學到的東西仍是不少的。git

以前主要是針對函數處理部分的API作解讀,通過那些天的努力,基本已經解讀完了,如今把重點移到數組上。對於數組處理API的解讀,從這篇文章開始。github

flatten是一個很基礎的函數,在Underscore中也算是一個工具函數,爲了方便之後的講解,今天先閱讀flatten函數的源碼。面試

首先,咱們帶着問題來閱讀源碼,若是你參加面試,考官讓你手寫一個展開數組的函數,你會怎麼寫?數組

實現一個flatten函數

咱們接受的參數應該是一個數組,咱們可使用一個叫array的變量表示它,它的返回值應該是一個數組,使用result表示:函數

function flatten(array) {
    var result = [];
    // ... 展開代碼
    return result
}
複製代碼

而後咱們應該對傳入的數組進行類型驗證,若是不是數組,咱們應該拋出一個類型異常:工具

function flatten(array) {
    var result = [];
    if(Object.prototype.toString.call(array) !== '[object Array]')
        throw new TypeError('Please pass a array-type object as parameter to flatten function');
    else {
        // ... 展開代碼
    }
    return result
}
複製代碼

這樣就能夠保證咱們接收到的參數是一個數組,接下來咱們應該遍歷array參數,對於它的每一項,若是不是數組,咱們就將其添加到result中,不然繼續展開:spa

function flatten(array) {
    var result = [];
    if(Object.prototype.toString.call(array) !== '[object Array]')
        throw new TypeError('Please pass a array-type object as parameter to flatten function');
    else {
        for(var i = 0; i < array.length; i++) {
            if(Object.prototype.toString.call(array[i]) === '[object Array]') {
                // ... 繼續展開。
            }
            else {
                result.push(array[i]);
            }
        }
    }
    return result
}
複製代碼

當數組中的項仍是一個數組時,咱們應當如何展開呢? 因爲不肯定究竟是嵌套了多少層數組,因此最好是使用遞歸來展開,可是有新的問題,咱們的flatten函數返回一個數組結果,可是咱們如何把遞歸結果返回給咱們的result呢,是使用concat方法仍是怎樣?prototype

因爲函數中對象類型的參數是引用傳值,因此咱們能夠把result傳遞給flatten自身,使其直接修改result便可:code

function flatten(array, result) {
    var result = result || [];
    if(Object.prototype.toString.call(array) !== '[object Array]')
        throw new TypeError('Please pass a array-type object as parameter to flatten function');
    else {
        for(var i = 0; i < array.length; i++) {
            if(Object.prototype.toString.call(array[i]) === '[object Array]') {
                // ... 遞歸展開。
                arguments.callee(array[i], result);
            }
            else {
                result.push(array[i]);
            }
        }
    }
    return result
}
複製代碼

以上函數,就基本實現了flatten的功能,再美化一下:對象

var flatten = function(array, result) {
    var result = result || [];
    var length = array.length;
    var toString = Object.prototype.toString;
    var type = toString.call(array);
    if(type !== '[object Array]')
        throw new TypeError('The parameter you passed is not a array');
    else {
        for(var i = 0; i < length; i++) {
            if(toString.call(array[i]) !== '[object Array]') {
                result.push(array[i]);
            }
            else {
                arguments.callee(array[i], result);
            }
        }
    }
    return result;
}
複製代碼

你們能夠把上面這段代碼拷貝到控制檯進行實驗。

Underscore中的flatten函數

經過咱們本身親手實現一個flatten函數,閱讀Underscore源碼就變得簡單了。 下面是Underscore中flatten函數的源碼(附註釋):

var flatten = function (input, shallow, strict, output) {
	output = output || [];
	var idx = output.length;
	//遍歷input參數。
	for (var i = 0, length = getLength(input); i < length; i++) {
		var value = input[i];
		if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
			// Flatten current level of array or arguments object.
			//若是input數組的元素是數組或者類數組對象,根據是否shallow來展開,若是shallow爲true,那麼只展開一級。
			if (shallow) {
				var j = 0, len = value.length;
				while (j < len) output[idx++] = value[j++];
			} else {
				//若是shallow爲false,那麼遞歸展開全部層級。
				flatten(value, shallow, strict, output);
				idx = output.length;
			}
		} else if (!strict) {
			//若是value不是數組或類數組對象,而且strict爲false。
			//那麼直接將value添加到輸出數組,不然忽略value。
			output[idx++] = value;
		}
	}
	return output;
};
複製代碼

Underscore實現的flatten更增強大,它支持類數組對象而不單單是數組,而且它多了兩個參數——shallow和strict。

當shallow爲true時,flatten只會把輸入數組的數組子項展開一級,若是shallow爲false,那麼會所有展開。

當strict爲false時,只要是非數組對象,flatten都會直接添加到output數組中;若是strict爲true,那麼會無視input數組中的非類數組對象。

更多Underscore源碼解讀:GitHub

相關文章
相關標籤/搜索