成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 中如何實(shí)現(xiàn)函數(shù)隊(duì)列?(一)

Kyxy / 1042人閱讀

摘要:相反,我們只需要在末尾里找出中的下一個(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、fn2fn3需要按順序調(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í)行fn3fn3執(zhí)行完了然才真正輪到fn2Timeout。
怎么解決?我們分析下,這里的關(guān)鍵在于fn2Timeout,我們必須等到它真正執(zhí)行完才調(diào)用fn3,理想情況下大概像這樣:

function fn2() {
    setTimeout(function() {
        fn2Timeout();
        fn3();
    }, 0);
}

但這樣做相當(dāng)于把原來的fn2Timeout整個(gè)拿掉換成一個(gè)新函數(shù),再把原來的fn2Timeoutfn3插進(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末尾里找出stackfn2的下一個(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)用一次nextnext會(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

相關(guān)文章

  • 談?wù)勎覍?duì)js定時(shí)器的點(diǎn)理解

    摘要:這兩個(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í)器在單線程中工作,它們表...

    frontoldman 評(píng)論0 收藏0
  • 事件循環(huán)與任務(wù)隊(duì)列

    摘要:需要注意的是,定時(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 一...

    SQC 評(píng)論0 收藏0
  • JavaScript引擎是如何工作的?從調(diào)用棧到Promise你需要知道的

    摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。單線程的我們說是單線程的,因?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...

    Simon_Zhou 評(píng)論0 收藏0
  • JS 異步的實(shí)現(xiàn)

    摘要:由于引擎同一時(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í)...

    sihai 評(píng)論0 收藏0
  • 總結(jié)javascript基礎(chǔ)概念(二):事件隊(duì)列循環(huán)

    摘要:而事件循環(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)?有什么解決方...

    zhkai 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<