來聊一道前端面試題吧

前言

金三銀四,技術論壇上諸如:阿里、頭條、騰訊….面經層出不窮,朋友圈不少小夥伴都在找工做也遇到了各類各樣的麻煩。本文但願那些在準備面試的過程當中蕉綠的童鞋別僵化了本身的思惟,以本身曾經碰見到一道面試題爲引,用本身對待問題的想法行文,天馬行空,從僵硬的知識點中跳脫出來一塊兒思考,內容簡單易懂。評論區有不少的同窗留下了許多很棒的思路,你們不要錯過喲,歡迎你們一塊兒繼續交流學習。javascript

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)能順序輸出一、二、3.

複製代碼

題目簡析

咱們觀察 fucArr 每個子項都具備以下結構:前端

  1. 接收一個方法 next
  2. 有一個計時器,計時器回調方法體內對應着相應的輸出
  3. 輸出結束調用 next 方法。

他們的差別就是:計時器時間逐個減小。java

直接循環調用 3 個方法確定是不可取的。爲了能按序輸出,函數的執行過程應該是上一個函數 console 以後, 再執行下一個函數,而接收的這個 next參數就是執行下一個方法的關鍵。由於是頭到尾依次調用,咱們就把fucArr 稱之爲一個隊列。面試

思路1、

咱們假象本身是個編譯器,而後把執行的過程進行單步拆解。express

  1. fucArr 是作先執行等待隊列第一個,等待中的函數隊列爲原函數隊列的slice(1);
  2. 等待next執行後,而後又執行等待函數隊列的第一個函數,等待中的函數隊列爲原函數隊列的slice(1);

聽着是否是很像一個遞歸的過程,沒錯,那咱們先用遞歸來實現一下redux

var run = arr => {
	// 遞歸語句千萬條,找到出口第一條,那我們判斷遞歸出口的條件就是等待隊列爲空
	if (arr.length === 0) return;
	// 好的,一句話執行過程寫完了
	arr[0](() => run(arr.slice(1)));
}
run(fucArr)

// 1 2 3;
複製代碼

思路2、

如今咱們從遞歸的思路中跳脫出來,換種思路繼續思考.....閉包

上一個函數執行到某個時機觸發了下一個函數的執行。app

也就是說上一個函數 trigger,下一個函數纔開始執行。koa

根據描述 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,最終,依次輸出一、二、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
複製代碼

總結

其實還能夠聊下expresskoa中間件compose的思路,可是沒有必要(汪汪大笑.gif)。本文主旨也不是灌輸這個題目的解法,只是但願你們未來在面試和工做中遇到問題嘗試着用本身構建的知識體系去解決積極面對,最後祝小夥伴們找工做順利。

相關文章
相關標籤/搜索