來聊一道前端面試題吧
金三銀四,技術論壇上諸如:阿里、頭條、騰訊….面經層出不窮,朋友圈很多小夥伴都在找工作也遇到了各種各樣的麻煩。本文希望那些在準備面試的過程中蕉綠的童鞋別僵化了自己的思維,以自己曾經遇見到一道面試題為引,用自己對待問題的想法行文,天馬行空,從僵硬的知識點中跳脫出來一起思考,內容簡單易懂
。
題
const fucArr = [ next => { setTimeout(() => { console.log(1); next() }, 300) }, next => { setTimeout(() => { console.log(2); next() }, 200) }, next => { setTimeout(() => { console.log(3); next() }, 100) } ] var run = arr=>{ } // 實現一個run方法,使得run(fucArr)能順序輸出1、2、3. 複製程式碼
題目簡析
我們觀察fucArr
每一個子項都具有如下結構:
next next
他們的差異就是:計時器時間逐個減少。
直接迴圈呼叫3
個方法肯定是不可取的。為了能按序輸出,函式的執行過程應該是上一個函式console
之後, 再執行下一個函式,而接收的這個next
引數就是執行下一個方法的關鍵。因為是頭到尾依次呼叫,我們就把fucArr
稱之為一個佇列。
思路一、
我們假象自己是個編譯器,然後把執行的過程進行單步拆解。
fucArr
聽著是不是很像一個遞迴的過程,沒錯,那我們先用遞迴來實現一下
var run = arr => { // 遞迴語句千萬條,找到出口第一條,那咱們判斷遞迴出口的條件就是等待佇列為空 if (arr.length === 0) return; // 好的,一句話執行過程寫完了 arr[0](() => run(arr.slice(1))); } run(fucArr) // 1 2 3; 複製程式碼
思路二、
現在我們從遞迴的思路中跳脫出來,換種思路繼續思考.....
上一個函式執行到某個時機觸發了下一個函式的執行。
也就是說上一個函式trigger
,下一個函式才開始執行。
根據描述trigger
實際上做的就是觸發等待佇列的第一個函式的執行,因此我們可以如下定義。
var run = arr => { const trigger = () => { if (arr.length === 0) return; arr.shift()(); } } 複製程式碼
那麼trigger
何時進行呼叫呢?很顯然, 上一個函式式通過next
去觸發下一個函式呼叫,因此trigger
應該就是函式接收的next
,我們為了方便引數繫結需要重構一下咱們的等待佇列函式。當然不要忘了,首次執行要手動trigger
一下喔。
var run = arr => { const trigger = () => { if (arr.length === 0) return; arr.shift()(); } arr = arr.map(val => { return () => val(trigger); }) trigger(); } 複製程式碼
其實做引數繫結還有一種更優雅一點的方式,bind,所以大家注意咯,bind不單單能繫結this喔。
我們可以稍微改動一下:
var run = arr => { const trigger = () => { if (arr.length === 0) return; arr.shift()(); } arr = arr.map(val => { return val.bind(null, trigger); }) trigger(); } 複製程式碼
都9102年了,既然是前端面試那肯定少不了Promise
的對吧,那我們可不可以摻入一些Promise
的元素在裡面呢?答案是必然的。
根據Promise
的特性,當本身狀態改變,去觸發then
裡的方法(這裡不要深究這句話,意思瞭解就好)。是resolve
作為本身狀態改動的方法。那狀態改變是去做什麼事呢?好的,沒錯trigger
。那何時狀態改變呢?上一個函式next
呼叫的時候。
var run = arr => { const trigger = () => { if (arr.length === 0) return; arr.shift()(); } arr = arr.map(val => { return () => new Promise(resolve => { val(resolve) }).then(trigger); }) trigger(); } 複製程式碼
redux的思路、
現在繼續清空上面的思路,不要被幹擾。
首先給applymiddleware
(以下簡稱amw
)一個簡單的定義,amw
是接收若干個函式作為引數,最終會返回一個函式,這個函式呼叫,會按照順序,依次執行前面作為引數傳入的函式。為了不把問題複雜化,請接收我的誤導
引導,不要懷疑。
以下是作為引數傳入的函式要求的結構以下稱a
結構:
store=>next=>action=>{ // dosomething... next() } 複製程式碼
a
結構在第一次呼叫時,會返回一個方法,第二次呼叫時返回第二個方法,我們先來看原始碼的一個操作過程。
const chain = middlewares.map(middleware => middleware(middlewareAPI)) 複製程式碼
首先是一層迴圈呼叫,使得函式體變為b
結構:
next=>action=>{ // dosomething... next() } 複製程式碼
這樣做是為了以閉包的形式在dosomething
中能夠使用到middlewareApi
。
根據b
結構我們可以稍稍改變下原題 :
const fucArr = [ next => action => { setTimeout(() => { console.log(action++); next(action) }, 300) }, next => action => { setTimeout(() => { console.log(action++); next(action) }, 200) }, next => action => { setTimeout(() => { console.log(action++); next(action) }, 100) } ] var run = arr=>{ } // 實現一個run方法,run方法接收fucArr為引數;返回一個函式,這個函式接收一個引數1,最終,依次輸出1、2、3 // run(fucArr)(1) => 1 2 3 複製程式碼
變題相對於多了一個引數傳遞的過程,實際上我們需要順序執行的其實是結構c:
action=>{ // dosomething... next() } 複製程式碼
這些關鍵還是要如何構建每個函式接收的引數next
。
我們做如下假設,當fucArr
只有一個函式時 返回的就應該是:
fucArr[0](()=>{}) // 為了避免報錯,next應為一個空函式 // 即: action => { setTimeout(() => { console.log(action++); //(()=>{}) 這玩意兒就是接收的next (()=>{})(action) }, 300) } 複製程式碼
當fucArr
有兩個函式時返回:
fucArr[0](fucArr[1](()=>{})) // 即: action => { setTimeout(() => { console.log(action++); fucArr[1](()=>{})(action) }, 300) } 複製程式碼
當有三個函式的時返回:
fucArr[0](fucArr[1](fucArr[2](()=>{})) 複製程式碼
仔細觀察返回函式的結構發現,所有的函式都是接受上一個函式呼叫後的返回值(以下稱模式1),最後一個函式接收的是一個空函式。我們嘗試構建模式1:
// 首先初始想法模型是這樣的 // 但是由於咱們是程式執行,不能像上面咱們描述問題的時候,繼續往next裡塞函式。 // 而在遍歷到 next 的下一個函式的時當前是無法明確next應該是什麼,因此我們需要將模式改變一下。 pre(next()); // 當遍歷到next下一個節點時,把當前函式作為arg傳入進來 arg=>pre(next(arg)) 複製程式碼
pre
+next
+ 遍歷,這三個關鍵詞沒錯,就是reduce。因此:
var reduceResult = fucArr.reduce((pre,next)=> (...arg)=>pre(next(...arg))); // 我們發現這個返回的還是一個 arg=>pre(next(arg)) 這樣模式的函式,接收的引數任然是一個函式。 // 於是乎真的需要返回的函式其實是 return reduceResult(()=>{}); 複製程式碼
所以最終形態是
var run = arr=>{ var reduceResult = arr.reduce((pre,next)=> (...arg)=>pre(next(...arg))); return reduceResult(()=>{}); } run(fucArr)(1); // 1 2 3 複製程式碼
總結
其實還可以聊下express
、koa
中介軟體compose
的思路,但是沒有必要(汪汪大笑.gif)。本文主旨也不是灌輸這個題目的解法,只是希望大家將來在面試和工作中遇到問題嘗試著用自己構建的知識體系去解決積極面對,最後祝小夥伴們找工作順利。