最近是在所在實習公司的第一個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