面試題目別有洞天:優雅es6+智慧polyfill+redux迷之命名

以前的一篇文章:從一道面試題,到「我可能看了假源碼」討論了bind方法的各類進階Pollyfill,今天再分享一個有意思的題目。javascript

從解這道題目出發,我會談到數組的Reduce方法,ES6特性和Redux數據流框架中Reducer的命名等等。一道典型的題目,卻如唐代詩人章碣《對月》詩中所云:「別有洞天三十六,水晶臺殿冷層層。」前端

題目背景

完成一個'flatten'的函數,實現「拍平」一個多維數組爲一維。示例以下:java

var testArr1 = [[0, 1], [2, 3], [4, 5]];
var testArr2 = [0, [1, [2, [3, [4, [5]]]]]];
flatten(testArr1) // [0, 1, 2, 3, 4, 5]
flatten(testArr2) // [0, 1, 2, 3, 4, 5]複製代碼

解法先睹爲快

先看一眼比較優雅的ES6解法:react

const flatten = arr => arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatten(val) : val), []);複製代碼

若是你看不明白,不要放棄。我會用ES5的思路「翻譯」一下,相信你很快就能看懂。git

若是你一眼能看明白,也建議繼續往下讀。由於會有「不同」的知識點。es6

深刻解讀

第一個想到的念頭確定是遞歸,遞歸天然就想到遞歸的「盡頭」,那就要判斷數組某項元素是否仍是數組類型。
好吧,咱們開始動手實現一個方案,實際上是上面解法的ES5版本:github

var flatten = function(array) {
    return array.reduce(function(previous, val) {
        if (Object.prototype.toString.call(val) !== '[object Array]') {
            return (previous.push(val), previous);
        }
        return (Array.prototype.push.apply(previous, flatten(val)), previous);
    }, []);
};複製代碼

可能這樣寫,對於不少人來講,並不能徹底理解。由於咱們使用了較多JS高級用法。關鍵核心還用到了相似「函數式」思想的reduce方法。
千萬不要灰心,繼續往下看。面試

return的究竟是什麼?

咱們注意到上面的寫法return使用了()表達式。括號內容前半句是爲了執行。這樣寫也許稍微晦澀難懂一些。請看下面的代碼示例,你就會明白:編程

function t() {
    var a = 1;
    return (a++, a);
}
t(); // 2複製代碼

Object.prototype.toString.call是什麼?

Object.prototype.toString.call能夠暫且認爲是「功能最強大」的類型判斷語句。在對數組類型進行判斷時,須要格外當心,好比這樣幾個「陷阱」:redux

var a = [];
typeof a; // "object"
a instanceof Array; // true;
Object.prototype.toString.call(a); // "[object Array]"複製代碼

reduce方法到底作了什麼?

如今到了最關鍵的地方。reduce方法是ES5引入,不少人使用它的場景並很少。可是瞭解他的特性倒是必須的。遺憾的是,社區上對於它的內容彷佛都不是「過重視」。「函數式「思想也讓一些初學者望而卻步。這裏我簡要進行「科普」,由於下面我要圍繞它進行延伸:

reduce在英文中譯爲「減小; 縮小; 使還原; 使變弱」,MDN對方法直述爲:「The reduce method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.」
我並不打算對他直接翻譯,由於這樣會變的更加晦澀難懂。

咱們看他的使用語法:

array1.reduce(callbackfn[, initialValue])複製代碼

參數分析:

1)array1:必需。
一個數組對象。即調用reduce方法的必須是一個數組類型。

2)callbackfn:必需。
一個接受最多四個參數的函數。對於數組中的每一個元素,reduce方法都會調用 callbackfn 函數一次。
這個callback的4個參數爲:

accumulator // 上一次調用回調返回的值,或者是提供的初始值(initialValue)
currentValue // 數組中正在處理的元素
currentIndex // 數據中正在處理的元素索引,若是提供了initialValue ,從0開始;不然從1開始
array // 調用reduce的數組複製代碼

3)initialValue可選項。
其值用於第一次調用callback的第一個參數。若是此參數爲空,則拿數組第一項來做爲第一次調用callback的第一個參數。

好比,咱們分析一個經常使用用法:

[0,1,2,3,4].reduce(function(previous, item, currentIndex, array){
  return previous + item;
});
// 10複製代碼

這裏並未提供reduce的第二個參數initialValue,因此從數組第一項開始進行回調函數的執行。而且每次回調函數執行完以後的結果,做爲下一次的previous執行回調。

因此,上述代碼即是一個累加器的實現。

ES6寫法

如今理解了Reduce函數,再結合ES6特性,使解法更加優雅:

const flatten = arr => arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatten(val) : val), []);複製代碼

這樣寫是否是太「函數式」了,可是思路跟以前解法徹底同樣。我只不過充分使用了箭頭函數帶來的便利。而且使用了更便捷的isArray對數組類型進行判斷。這是開篇提到的解法,也是MDN最新版的實現。

如何實現一個reduce的pollyfill

如今明白了reduce的祕密,接下來咱們須要充分發揮對JS的理解,來手動實現一個reduce函數。畢竟,reduce是ES5帶來的數組新特性,在不使用ES5-shim的狀況下,須要手動兼容。另外,其實reduce方法能夠實現的邏輯,大多都可以使用循環來實現。可是瞭解這樣一個優雅的方法,不論是在程序的可讀性上,仍是在設計理解層面上,仍是頗有必要的。

一樣,在MDN上也有實現,可是我以爲下面的代碼實現更加優雅和清晰:

var reduce = function(arr, func, initialValue) {
    var base = typeof initialValue === 'undefined' ? arr[0] : initialValue;
    var startPoint = typeof initialValue === 'undefined' ? 1 : 0;
    arr.slice(startPoint)
        .forEach(function(val, index) {
            base = func(base, val, index + startPoint, arr);
        });
    return base;
};複製代碼

若是讀者有不一樣實現思路,也歡迎與我討論。

ES5-shim的pollyfill

我也一樣看了下ES5-shim裏的pollyfill,跟個人思路基本徹底一致。惟一有一點區別的地方在於我用了forEach迭代而ES5-shim使用的是簡單for循環。

固然,數組的forEach方法也是ES5新增的。但我這裏是爲了用簡單明瞭的思路,實現reduce方法,根本目的仍是但願對reduce有一個全面透徹的瞭解。

若是您還不明白,我認爲仍是對於reduce方法沒有掌握透徹。建議再梳理一遍。

Redux中的reducer

明白了reduce函數,咱們再來看一下Redux中的reducer和這個reduce有什麼命名上的關聯。

熟悉Redux數據流架構的同窗理解reducer作了什麼,關於這個純函數的命名,在redux源碼github倉庫上也有一個官方解釋:「It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue)」,雖然是一筆帶過,可是總結的恰到好處。

我詳細說一下:Redux數據流裏,reducers實際上是根據以前的狀態(previous state)和現有的action(current action)更新state(這個state能夠理解爲上文累加器的結果(accumulation))。每次redux reducer被執行時,state和action被傳入,這個state根據action進行累加或者是「自身消減」(reduce,英文原意),進而返回最新的state。這符合一個典型reduce函數的用法:state -> action -> state.

總結

這篇文章對於如何優雅地「扁平化」一個多維數組進行了解法分析。而且對於秉承函數式編程思想的reduce方法進行了深刻討論,咱們還實現了reduce的pollyfill。在充分理解的基礎上,又簡要延伸到redux數據架構裏面reducer的命名。熟悉Redux的同窗必定會有所感觸。

最後但願對讀者有所啓發,也歡迎同我討論。

PS:百度知識搜索部大前端繼續招兵買馬,高級工程師、實習生職位均有,有意向者火速聯繫。。。


本文對你有幫助?歡迎掃碼加入前端學習小組微信羣:

相關文章
相關標籤/搜索