摘要:相反,我們只需要在末尾里找出中的下一個(gè)函數(shù),再調(diào)用第二個(gè)調(diào)用這個(gè)函數(shù)負(fù)責(zé)找出中的下一個(gè)函數(shù)并執(zhí)行。我們現(xiàn)在來實(shí)現(xiàn)其實(shí)也可以用把拿出來通過去獲取中的函數(shù),每調(diào)用一次會(huì)加,從而達(dá)到取出下一個(gè)函數(shù)的目的。中大名鼎鼎的框架正是這樣實(shí)現(xiàn)中間件隊(duì)列的。
假設(shè)你有幾個(gè)函數(shù)fn1、fn2和fn3需要按順序調(diào)用,最簡(jiǎn)單的方式當(dāng)然是:
fn1(); fn2(); fn3();
但有時(shí)候這些函數(shù)是運(yùn)行時(shí)一個(gè)個(gè)添加進(jìn)來的,調(diào)用的時(shí)候并不知道都有些什么函數(shù);這個(gè)時(shí)候可以預(yù)先定義一個(gè)數(shù)組,添加函數(shù)的時(shí)候把函數(shù)push 進(jìn)去,需要的時(shí)候從數(shù)組中按順序一個(gè)個(gè)取出來,依次調(diào)用:
var stack = []; // 執(zhí)行其他操作,定義fn1 stack.push(fn1); // 執(zhí)行其他操作,定義fn2、fn3 stack.push(fn2, fn3); // 調(diào)用的時(shí)候 stack.forEach(function(fn) { fn() });
這樣函數(shù)有沒名字也不重要,直接把匿名函數(shù)傳進(jìn)去也可以。來測(cè)試一下:
var stack = []; function fn1() { console.log("第一個(gè)調(diào)用"); } stack.push(fn1); function fn2() { console.log("第二個(gè)調(diào)用"); } stack.push(fn2, function() { console.log("第三個(gè)調(diào)用") }); stack.forEach(function(fn) { fn() }); // 按順序輸出"第一個(gè)調(diào)用"、"第二個(gè)調(diào)用"、"第三個(gè)調(diào)用"
這個(gè)實(shí)現(xiàn)目前為止工作正常,但我們忽略了一個(gè)情況,就是異步函數(shù)的調(diào)用。異步是JavaScript 中無法避免的一個(gè)話題,這里不打算探討JavaScript 中有關(guān)異步的各種術(shù)語和概念,請(qǐng)讀者自行查閱(例如某篇著名的評(píng)注)。如果你知道下面代碼會(huì)輸出1、3、2,那請(qǐng)繼續(xù)往下看:
console.log(1); setTimeout(function() { console.log(2); }, 0); console.log(3);
假如stack 隊(duì)列中有某個(gè)函數(shù)是類似的異步函數(shù),我們的實(shí)現(xiàn)就亂套了:
var stack = []; function fn1() { console.log("第一個(gè)調(diào)用") }; stack.push(fn1); function fn2() { setTimeout(function fn2Timeout() { console.log("第二個(gè)調(diào)用"); }, 0); } stack.push(fn2, function() { console.log("第三個(gè)調(diào)用") }); stack.forEach(function(fn) { fn() }); // 輸出"第一個(gè)調(diào)用"、"第三個(gè)調(diào)用"、"第二個(gè)調(diào)用"
問題很明顯,fn2確實(shí)按順序調(diào)用了,但setTimeout里的function fn2Timeout() { console.log("第二個(gè)調(diào)用") }卻不是立即執(zhí)行的(即使把timeout 設(shè)為0);fn2調(diào)用之后馬上返回,接著執(zhí)行fn3,fn3執(zhí)行完了然才真正輪到fn2Timeout。
怎么解決?我們分析下,這里的關(guān)鍵在于fn2Timeout,我們必須等到它真正執(zhí)行完才調(diào)用fn3,理想情況下大概像這樣:
function fn2() { setTimeout(function() { fn2Timeout(); fn3(); }, 0); }
但這樣做相當(dāng)于把原來的fn2Timeout整個(gè)拿掉換成一個(gè)新函數(shù),再把原來的fn2Timeout和fn3插進(jìn)去。這種動(dòng)態(tài)改掉原函數(shù)的寫法有個(gè)專門的名詞叫Monkey Patch。按我們程序員的口頭禪:“做肯定是能做”,但寫起來有點(diǎn)擰巴,而且容易把自己繞進(jìn)去。有沒更好的做法?
我們退一步,不強(qiáng)求等fn2Timeout完全執(zhí)行完才去執(zhí)行fn3,而是在fn2Timeout函數(shù)體的最后一行去調(diào)用:
function fn2() { setTimeout(function fn2Timeout() { console.log("第二個(gè)調(diào)用"); fn3(); // 注{1} }, 0); }
這樣看起來好了點(diǎn),不過定義fn2的時(shí)候都還沒有fn3,這fn3哪來的?
還有一個(gè)問題,fn2里既然要調(diào)用fn3,那我們就不能通過stack.forEach去調(diào)用fn3了,否則fn3會(huì)重復(fù)調(diào)用兩次。
我們不能把fn3寫死在fn2里。相反,我們只需要在fn2Timeout末尾里找出stack中fn2的下一個(gè)函數(shù),再調(diào)用:
function fn2() { setTimeout(function fn2Timeout() { console.log("第二個(gè)調(diào)用"); next(); }, 0); }
這個(gè)next函數(shù)負(fù)責(zé)找出stack 中的下一個(gè)函數(shù)并執(zhí)行。我們現(xiàn)在來實(shí)現(xiàn)next:
var index = 0; function next() { var fn = stack[index]; index = index + 1; // 其實(shí)也可以用shift 把fn 拿出來 if (typeof fn === "function") fn(); }
next通過stack[index]去獲取stack中的函數(shù),每調(diào)用next一次index會(huì)加1,從而達(dá)到取出下一個(gè)函數(shù)的目的。
next這樣使用:
var stack = []; // 定義index 和next function fn1() { console.log("第一個(gè)調(diào)用"); next(); // stack 中每一個(gè)函數(shù)都必須調(diào)用`next` }; stack.push(fn1); function fn2() { setTimeout(function fn2Timeout() { console.log("第二個(gè)調(diào)用"); next(); // 調(diào)用`next` }, 0); } stack.push(fn2, function() { console.log("第三個(gè)調(diào)用"); next(); // 最后一個(gè)可以不調(diào)用,調(diào)用也沒用。 }); next(); // 調(diào)用next,最終按順序輸出"第一個(gè)調(diào)用"、"第二個(gè)調(diào)用"、"第三個(gè)調(diào)用"。
現(xiàn)在stack.forEach一行已經(jīng)刪掉了,我們自行調(diào)用一次next,next會(huì)找出stack中的第一個(gè)函數(shù)fn1執(zhí)行,fn1 里調(diào)用next,去找出下一個(gè)函數(shù)fn2并執(zhí)行,fn2里再調(diào)用next,依此類推。
每一個(gè)函數(shù)里都必須調(diào)用next,如果某個(gè)函數(shù)里不寫,執(zhí)行完該函數(shù)后程序就會(huì)直接結(jié)束,沒有任何機(jī)制繼續(xù)。
了解了函數(shù)隊(duì)列的這個(gè)實(shí)現(xiàn)后,你應(yīng)該可以解決下面這道面試題了:
// 實(shí)現(xiàn)一個(gè)LazyMan,可以按照以下方式調(diào)用: LazyMan(“Hank”) /* 輸出: Hi! This is Hank! */ LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出 /* 輸出: Hi! This is Hank! // 等待10秒.. Wake up after 10 Eat dinner~ */ LazyMan(“Hank”).eat(“dinner”).eat(“supper”) /* 輸出: Hi This is Hank! Eat dinner~ Eat supper~ */ LazyMan(“Hank”).sleepFirst(5).eat(“supper”) /* 等待5秒,輸出 Wake up after 5 Hi This is Hank! Eat supper */ // 以此類推。
Node.js 中大名鼎鼎的connect框架正是這樣實(shí)現(xiàn)中間件隊(duì)列的。有興趣可以去看看它的源碼或者這篇解讀《何為 connect 中間件》。
細(xì)心的你可能看出來,這個(gè)next暫時(shí)只能放在函數(shù)的末尾,如果放在中間,原來的問題還會(huì)出現(xiàn):
function fn() { console.log(1); next(); console.log(2); // next()如果調(diào)用了異步函數(shù),console.log(2)就會(huì)先執(zhí)行 }
redux 和koa 通過不同的實(shí)現(xiàn),可以讓next放在函數(shù)中間,執(zhí)行完后面的函數(shù)再折回來執(zhí)行next下面的代碼,非常巧妙。有空再寫寫。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81491.html
摘要:這兩個(gè)函數(shù)接受定時(shí)器的例如我們上面提到的兩個(gè)函數(shù)產(chǎn)生的定時(shí)器,并停止對(duì)定時(shí)器中指定函數(shù)的調(diào)用。注意,定時(shí)器雖然觸發(fā)了,但是并不會(huì)立即執(zhí)行,它只是把需要延遲執(zhí)行的函數(shù)加入了執(zhí)行隊(duì)列,在線程的某一個(gè)可用的時(shí)間點(diǎn),這個(gè)函數(shù)就能夠得到執(zhí)行。 擼了今年阿里、頭條和美團(tuán)的面試,我有一個(gè)重要發(fā)現(xiàn)....... javascript定時(shí)器工作原理是一個(gè)重要的基礎(chǔ)知識(shí)點(diǎn)。因?yàn)槎〞r(shí)器在單線程中工作,它們表...
摘要:需要注意的是,定時(shí)器比較特殊,并沒有把回調(diào)函數(shù)掛在事件循環(huán)隊(duì)列中,它所做的就是設(shè)置一個(gè)定時(shí)器,當(dāng)定時(shí)器到時(shí)后,環(huán)境會(huì)把你的回調(diào)函數(shù)放在事件循環(huán)中,這樣,在未來某個(gè)時(shí)刻的會(huì)被取出執(zhí)行。 Author: bugall Wechat: bugallF Email: [email protected] Github: https://github.com/bugall 一...
摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。單線程的我們說是單線程的,因?yàn)橛幸粋€(gè)調(diào)用棧處理我們的函數(shù)。也就是說,如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開調(diào)用棧的。每個(gè)異步函數(shù)在被送入調(diào)用棧之前必須通過回調(diào)隊(duì)列。 翻譯:瘋狂的技術(shù)宅原文:https://www.valentinog.com/bl... 本文首發(fā)微信公眾號(hào):前端先鋒歡迎關(guān)注,每天都給你推送新鮮的前端技術(shù)文章 sh...
摘要:由于引擎同一時(shí)間只執(zhí)行一段代碼這是由單線程的性質(zhì)決定的,所以每個(gè)代碼塊阻塞了其它異步事件的進(jìn)行。這意味著瀏覽器將等待著一個(gè)新的異步事件發(fā)生。異步的任務(wù)執(zhí)行的順序是不固定的,主要看返回的速度。 我們經(jīng)常說JS是單線程的,比如node.js研討會(huì)上大家都說JS的特色之一是單線程的,這樣使JS更簡(jiǎn)單明了,可是大家真的理解所謂JS的單線程機(jī)制嗎?單線程時(shí),基于事件的異步機(jī)制又該當(dāng)如何,這些知識(shí)...
摘要:而事件循環(huán)是主線程中執(zhí)行棧里的代碼執(zhí)行完畢之后,才開始執(zhí)行的。由此產(chǎn)生的異步事件執(zhí)行會(huì)作為任務(wù)隊(duì)列掛在當(dāng)前循環(huán)的末尾執(zhí)行。在下,觀察者基于監(jiān)聽事件的完成情況在下基于多線程創(chuàng)建。 主要問題: 1、JS引擎是單線程,如何完成事件循環(huán)的? 2、定時(shí)器函數(shù)為什么計(jì)時(shí)不準(zhǔn)確? 3、回調(diào)與異步,有什么聯(lián)系和不同? 4、ES6的事件循環(huán)有什么變化?Node中呢? 5、異步控制有什么難點(diǎn)?有什么解決方...
閱讀 2118·2021-11-05 09:42
閱讀 2861·2021-09-23 11:21
閱讀 2857·2019-08-30 14:00
閱讀 3323·2019-08-30 13:15
閱讀 471·2019-08-29 17:18
閱讀 3563·2019-08-29 16:29
閱讀 2762·2019-08-29 14:06
閱讀 2803·2019-08-23 14:41