理解Underscore中的flatten函式

Russ_Zhong發表於2018-03-31

最近是在所在實習公司的第一個sprint,有個朋友又請假了,所以任務比較重,一直這麼久都沒怎麼更新了,這個週末賴了個床,糾結了一會兒決定還是繼續寫這個系列,雖然比較乏味,但是學到的東西還是很多的。

之前主要是針對函式處理部分的API做解讀,經過那些天的努力,基本已經解讀完了,現在把重點移到陣列上。對於陣列處理API的解讀,從這篇文章開始。

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中,否則繼續展開:

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方法還是怎樣?

由於函式中物件型別的引數是引用傳值,所以我們可以把result傳遞給flatten自身,使其直接修改result即可:

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

相關文章