來聊一道前端面試題吧
前言
金三銀四,技術論壇上諸如:阿里、頭條、騰訊….面經層出不窮,朋友圈很多小夥伴都在找工作也遇到了各種各樣的麻煩。本文希望那些在準備面試的過程中蕉綠的童鞋別僵化了自己的思維,以自己曾經遇見到一道面試題為引,用自己對待問題的想法行文,天馬行空,從僵硬的知識點中跳脫出來一起思考,內容簡單易懂。評論區有很多的同學留下了許多很棒的思路,大家不要錯過喲,歡迎大家一起繼續交流學習。
“我自己是一名從事了8年web前端開發的老程式設計師(我的微信:webxxq),今年年初我花了一個月整理了一份最適合2020年自學的web前端全套培訓教程((視訊+筆記+素材+原始碼+專案實戰),從最基礎的HTML+CSS+JS到移動端HTML5以及各種框架和新技術都有整理,打包給每一位前端小夥伴(總共約85G),這裡是前端學習者聚集地,歡迎初學和進階中的小夥伴(所有前端教程關注我的微信公眾號:web前端學習圈,關注後回覆“2020”即可領取)。
題
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
是做先執行等待佇列第一個,等待中的函式佇列為原函式佇列的slice(1);- 等待next執行後,然後又執行等待函式佇列的第一個函式,等待中的函式佇列為原函式佇列的slice(1);
聽著是不是很像一個遞迴的過程,沒錯,那我們先用遞迴來實現一下
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)。本文主旨也不是灌輸這個題目的解法,只是希望大家將來在面試和工作中遇到問題嘗試著用自己構建的知識體系去解決積極面對,最後祝小夥伴們找工作順利。
相關文章
- 從一道前端面試題談起前端面試題
- 【web前端面試題整理05】做幾道前端面試題休息休息吧Web前端面試題
- 面試官:聊一聊索引吧面試索引
- 一道前端面試題引發的學習前端面試題
- 一道有意思的微信前端面試題前端面試題
- 記一道控制並行數的前端面試題並行前端面試題
- 一道無聊的題目
- 從一道前端面試題引發的原理性探究前端面試題
- 面試官:我們來聊一聊Redis吧,你瞭解多少就答多少面試Redis
- 2019年前端面試都聊啥?一起來看看前端面試
- Hi,我們再來聊一聊Java的單例吧Java單例
- 五年前,我寫錯了一道面試題。面試題
- 前端面試題前端面試題
- 【面試】前端面試題前端面試題
- 總是在聊執行緒Thread,試試協程吧!執行緒thread
- 面試前如何準備才能提高成功率(含前端面試押題)面試前端
- 前端面試題一前端面試題
- 前端面試題整理前端面試題
- 前端面試題目前端面試題
- python後端面試題Python後端面試題
- 前端面試送命題:面試題篇前端面試題
- 【前端面試題】2023年前端面試真題之Vue篇前端面試題Vue
- 螢幕太大?來試試分屏吧
- 從一道面試題來學習前臺程式和後臺程式、孤兒程式和殭屍程式面試題
- 關於一道面試題的極其無聊的python演算法實現面試題Python演算法
- 前端面試題-display篇前端面試題
- 荔枝FM前端面試題前端面試題
- 前端面試題整理--http前端面試題HTTP
- 前端面試題收藏(1)前端面試題
- 前端面試典型例題前端面試
- 前端面試題-CSS Hack前端面試題CSS
- 前端面試題(總結)前端面試題
- 最新前端面試題攻略前端面試題
- 前端面試題 | CSS篇前端面試題CSS
- 前端面試題(附答案)前端面試題
- 前端面試題總結前端面試題
- 前端面試題(4)JavaScript前端面試題JavaScript
- 100個前端面試題前端面試題