国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript系列——JavaScript同步、異步、回調(diào)執(zhí)行順序之經(jīng)典閉包setTimeou

rockswang / 390人閱讀

摘要:同步異步回調(diào)傻傻分不清楚。分割線上面主要講了同步和回調(diào)執(zhí)行順序的問(wèn)題,接著我就舉一個(gè)包含同步異步回調(diào)的例子。同步優(yōu)先回調(diào)內(nèi)部有個(gè),第二個(gè)是一個(gè)回調(diào)回調(diào)墊底。異步也,輪到回調(diào)的孩子們回調(diào),出來(lái)執(zhí)行了。

同步、異步、回調(diào)?傻傻分不清楚。

大家注意了,教大家一道口訣:

同步優(yōu)先、異步靠邊、回調(diào)墊底(讀起來(lái)不順)

用公式表達(dá)就是:

同步 => 異步 => 回調(diào)

這口訣有什么用呢?用來(lái)對(duì)付面試的。

有一道經(jīng)典的面試題:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log("i: ",i);
    }, 1000);
}

console.log(i);

//輸出
5
i:  5
i:  5
i:  5
i:  5
i:  5

這道題目大家都遇到過(guò)了吧,那么為什么會(huì)輸出這個(gè)呢?記住我們的口訣 同步 => 異步 => 回調(diào)

1、for循環(huán)和循環(huán)體外部的console是同步的,所以先執(zhí)行for循環(huán),再執(zhí)行外部的console.log。(同步優(yōu)先)

2、for循環(huán)里面有一個(gè)setTimeout回調(diào),他是墊底的存在,只能最后執(zhí)行。(回調(diào)墊底)

那么,為什么我們最先輸出的是5呢?

非常好理解,for循環(huán)先執(zhí)行,但是不會(huì)給setTimeout傳參(回調(diào)墊底),等f(wàn)or循環(huán)執(zhí)行完,就會(huì)給setTimeout傳參,而外部的console打印出5是因?yàn)閒or循環(huán)執(zhí)行完成了。

知乎有大神講解過(guò) 80% 應(yīng)聘者都不及格的 JS 面試題 ,就是以這個(gè)例子為開(kāi)頭的。但是沒(méi)有說(shuō)為什么setTimeout是輸出5個(gè)5。

這里涉及到JavaScript執(zhí)行棧和消息隊(duì)列的概念,概念的詳細(xì)解釋可以看阮老師的 JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop - 阮一峰的網(wǎng)絡(luò)日志,或者看 并發(fā)模型與Event Loop

《圖片來(lái)自于MDN官方》

我拿這個(gè)例子做一下講解,JavaScript單線程如何處理回調(diào)呢?JavaScript同步的代碼是在堆棧中順序執(zhí)行的,而setTimeout回調(diào)會(huì)先放到消息隊(duì)列,for循環(huán)每執(zhí)行一次,就會(huì)放一個(gè)setTimeout到消息隊(duì)列排隊(duì)等候,當(dāng)同步的代碼執(zhí)行完了,再去調(diào)用消息隊(duì)列的回調(diào)方法。

在這個(gè)經(jīng)典例子中,也就是說(shuō),先執(zhí)行for循環(huán),按順序放了5個(gè)setTimeout回調(diào)到消息隊(duì)列,然后for循環(huán)結(jié)束,下面還有一個(gè)同步的console,執(zhí)行完console之后,堆棧中已經(jīng)沒(méi)有同步的代碼了,就去消息隊(duì)列找,發(fā)現(xiàn)找到了5個(gè)setTimeout,注意setTimeout是有順序的。

那么,setTimeout既然在最后才執(zhí)行,那么他輸出的i又是什么呢?答案就是5。。有人說(shuō)不是廢話嗎?

現(xiàn)在告訴大家為什么setTimeout全都是5,JavaScript在把setTimeout放到消息隊(duì)列的過(guò)程中,循環(huán)的i是不會(huì)及時(shí)保存進(jìn)去的,相當(dāng)于你寫(xiě)了一個(gè)異步的方法,但是ajax的結(jié)果還沒(méi)返回,只能等到返回之后才能傳參到異步函數(shù)中。
在這里也是一樣,for循環(huán)結(jié)束之后,因?yàn)閕是用var定義的,所以var是全局變量(這里沒(méi)有函數(shù),如果有就是函數(shù)內(nèi)部的變量),這個(gè)時(shí)候的i是5,從外部的console輸出結(jié)果就可以知道。那么當(dāng)執(zhí)行setTimeout的時(shí)候,由于全局變量的i已經(jīng)是5了,所以傳入setTimeout中的每個(gè)參數(shù)都是5。很多人都會(huì)以為setTimeout里面的i是for循環(huán)過(guò)程中的i,這種理解是不對(duì)的。

===========================================分割線=========================================

看了上面的解釋,你是不是有點(diǎn)頭暈,沒(méi)事,繼續(xù)深入講解。

我們給第一個(gè)例子加一行代碼。

for (var i = 0; i < 5; ++i) {
    setTimeout(function() {
        console.log("2: ",i);
    }, 1000);
    console.log("1: ", i); //新加一行代碼
}

console.log(i);

//輸出
1:  0
1:  1
1:  2
1:  3
1:  4
5
2:  5
2:  5
2:  5
2:  5
2:  5

來(lái),大家再跟著我一起念一遍:同步 => 異步 => 回調(diào) (強(qiáng)化記憶)

這個(gè)例子可以很清楚的看到先執(zhí)行for循環(huán),for循環(huán)里面的console是同步的,所以先輸出,for循環(huán)結(jié)束后,執(zhí)行外部的console輸出5,最后再執(zhí)行setTimeout回調(diào) 55555。。。

=====================================分割線============================================

這么簡(jiǎn)單,不夠帶勁是不是,那么面試官會(huì)問(wèn),怎么解決這個(gè)問(wèn)題?

最簡(jiǎn)單的當(dāng)然是let語(yǔ)法啦。。

for (let i = 0; i < 5; ++i) {
    setTimeout(function() {
        console.log("2: ",i);
    }, 1000);
}

console.log(i);

//輸出
i is not defined
2:  0
2:  1
2:  2
2:  3
2:  4

咦,有同學(xué)問(wèn),為什么外部的i報(bào)錯(cuò)了呢?
又有同學(xué)問(wèn),你這個(gè)口訣在這里好像不適應(yīng)啊?

let是ES6語(yǔ)法,ES5中的變量作用域是函數(shù),而let語(yǔ)法的作用域是當(dāng)前塊,在這里就是for循環(huán)體。在這里,let本質(zhì)上就是形成了一個(gè)閉包。也就是下面這種寫(xiě)法一樣的意思。如果面試官對(duì)你說(shuō)用下面的這種方式,還有l(wèi)et的方式,你可以嚴(yán)肅的告訴他:這就是一個(gè)意思!這也就是為什么有人說(shuō)let是語(yǔ)法糖。

var loop = function (_i) {
    setTimeout(function() {
        console.log("2:", _i);
    }, 1000);
};

for (var _i = 0; _i < 5; _i++) {
    loop(_i);
}

console.log(i);

面試官總說(shuō)閉包、閉包、閉包,什么是閉包?后面再講。

寫(xiě)成ES5的形式,你是不是發(fā)現(xiàn)就適合我說(shuō)的口訣了?而用let的時(shí)候,你發(fā)現(xiàn)看不懂?那是因?yàn)槟銢](méi)有真正了解ES6的語(yǔ)法原理。

我們來(lái)分析一下,用了let作為變量i的定義之后,for循環(huán)每執(zhí)行一次,都會(huì)先給setTimeout傳參,準(zhǔn)確的說(shuō)是給loop傳參,loop形成了一個(gè)閉包,這樣就執(zhí)行了5個(gè)loop,每個(gè)loop傳的參數(shù)分別是0,1,2,3,4,然后loop里面的setTimeout會(huì)進(jìn)入消息隊(duì)列排隊(duì)等候。當(dāng)外部的console執(zhí)行完畢,因?yàn)閒or循環(huán)里的i變成了一個(gè)新的變量 _i ,所以在外部的console.log(i)是不存在的。

現(xiàn)在可以解釋閉包的概念了:當(dāng)內(nèi)部函數(shù)以某一種方式被任何一個(gè)外部函數(shù)作用域訪問(wèn)時(shí),一個(gè)閉包就產(chǎn)生了。

我知道你又要我解釋這句話了,loop(_i)是外部函數(shù),setTimeout是內(nèi)部函數(shù),當(dāng)setTimeout被loop的變量訪問(wèn)的時(shí)候,就形成了一個(gè)閉包。(別說(shuō)你又暈了?)

隨便舉個(gè)新的例子。

function t() {
    var a = 10;
    var b = function() {
        console.log(a);    
    }
    b();
}
t(); //輸出 10

跟我一起念口訣:同步 => 異步 => 回調(diào) (強(qiáng)化記憶)
先執(zhí)行函數(shù)t,然后js就進(jìn)入了t內(nèi)部,定義了一個(gè)變量,然后執(zhí)行函數(shù)b,進(jìn)入b內(nèi)部,然后打印a,這里都是同步的代碼,沒(méi)什么異議,那么這里怎么解釋閉包:函數(shù)t是外部函數(shù),函數(shù)b是內(nèi)部函數(shù),當(dāng)函數(shù)b被函數(shù)t的變量訪問(wèn)的時(shí)候,就形成了閉包。

========================================分割線==============================================

上面主要講了同步和回調(diào)執(zhí)行順序的問(wèn)題,接著我就舉一個(gè)包含同步、異步、回調(diào)的例子。

let a = new Promise(
  function(resolve, reject) {
    console.log(1)
    setTimeout(() => console.log(2), 0)
    console.log(3)
    console.log(4)
    resolve(true)
  }
)
a.then(v => {
  console.log(8)
})

let b = new Promise(
  function() {
    console.log(5)
    setTimeout(() => console.log(6), 0)
  }
)

console.log(7)

看到這個(gè)例子,千萬(wàn)不要害怕?,先讀一遍口訣:同步 => 異步 => 回調(diào) (強(qiáng)化記憶)

1、看同步代碼:a變量是一個(gè)Promise,我們知道Promise是異步的,是指他的then()和catch()方法,Promise本身還是同步的,所以這里先執(zhí)行a變量?jī)?nèi)部的Promise同步代碼。(同步優(yōu)先)

    console.log(1)
    setTimeout(() => console.log(2), 0) //回調(diào)
    console.log(3)
    console.log(4)

2、Promise內(nèi)部有4個(gè)console,第二個(gè)是一個(gè)setTimeout回調(diào)(回調(diào)墊底)。所以這里先輸出1,3,4回調(diào)的方法丟到消息隊(duì)列中排隊(duì)等著。

3、接著執(zhí)行resolve(true),進(jìn)入then(),then是異步,下面還有同步?jīng)]執(zhí)行完呢,所以then也滾去消息隊(duì)列排隊(duì)等候。(真可憐)(異步靠邊)
4、b變量也是一個(gè)Promise,和a一樣,執(zhí)行內(nèi)部的同步代碼,輸出5,setTimeout滾去消息隊(duì)列排隊(duì)等候。

5、最下面同步輸出7。

6、同步的代碼執(zhí)行完了,JavaScript就跑去消息隊(duì)列呼叫異步的代碼:異步,出來(lái)執(zhí)行了。這里只有一個(gè)異步then,所以輸出8。

7、異步也over,輪到回調(diào)的孩子們:回調(diào),出來(lái)執(zhí)行了。這里有2個(gè)回調(diào)在排隊(duì),他們的時(shí)間都設(shè)置為0,所以不受時(shí)間影響,只跟排隊(duì)先后順序有關(guān)。則先輸出a里面的回調(diào)2,最后輸出b里面的回調(diào)6。

8、最終輸出結(jié)果就是:1、3、4、5、7、8、2、6。

我們還可以稍微做一點(diǎn)修改,把a(bǔ)里面Promise的 setTimeout(() => console.log(2), 0)改成 setTimeout(() => console.log(2), 2),對(duì),時(shí)間改成了2ms,為什么不改成1試試呢?1ms的話,瀏覽器都還沒(méi)有反應(yīng)過(guò)來(lái)呢。你改成大于或等于2的數(shù)字就能看到2個(gè)setTimeout的輸出順序發(fā)生了變化。所以回調(diào)函數(shù)正常情況下是在消息隊(duì)列順序執(zhí)行的,但是使用setTimeout的時(shí)候,還需要注意時(shí)間的大小也會(huì)改變它的順序。

====================================分割線==================================================

口訣不一定是萬(wàn)能的,只能作為一個(gè)輔助,更重要的還是要理解JavaScript的運(yùn)行機(jī)制,才能對(duì)代碼執(zhí)行順序有清晰的路線。

還有async/await等其他異步的方案,不管是哪種異步,基本都適用這個(gè)口訣,對(duì)于新手來(lái)說(shuō),可以快速讀懂面試官出的js筆試題目。以后再也不用害怕做筆試題啦。

特殊情況下不適應(yīng)口訣的也很正常,JavaScript博大精深,不是一句話就能概括出來(lái)的。

最后,在跟著我念一遍口訣:同步 => 異步 => 回調(diào)

如果文章對(duì)你有幫助,請(qǐng)點(diǎn)擊一下推薦。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/82315.html

相關(guān)文章

  • JavaScript系列——JavaScript同步異步回調(diào)執(zhí)行順序經(jīng)典閉包setTimeou

    摘要:同步異步回調(diào)傻傻分不清楚。分割線上面主要講了同步和回調(diào)執(zhí)行順序的問(wèn)題,接著我就舉一個(gè)包含同步異步回調(diào)的例子。同步優(yōu)先回調(diào)內(nèi)部有個(gè),第二個(gè)是一個(gè)回調(diào)回調(diào)墊底。異步也,輪到回調(diào)的孩子們回調(diào),出來(lái)執(zhí)行了。 同步、異步、回調(diào)?傻傻分不清楚。 大家注意了,教大家一道口訣: 同步優(yōu)先、異步靠邊、回調(diào)墊底(讀起來(lái)不順) 用公式表達(dá)就是: 同步 => 異步 => 回調(diào) 這口訣有什么用呢?用來(lái)對(duì)付面試的...

    lewif 評(píng)論0 收藏0
  • JS筆記

    摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁(yè)面加載發(fā)生了什么這是一篇開(kāi)發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...

    rottengeek 評(píng)論0 收藏0
  • 前端基礎(chǔ)

    摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開(kāi)發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開(kāi)發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡(jiǎn)的前端持續(xù)集成的開(kāi)發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥(niǎo),不論是面試求職,還是日...

    graf 評(píng)論0 收藏0
  • 《Node.js設(shè)計(jì)模式》基于回調(diào)異步控制流

    摘要:編寫(xiě)異步代碼可能是一種不同的體驗(yàn),尤其是對(duì)異步控制流而言。回調(diào)函數(shù)的準(zhǔn)則在編寫(xiě)異步代碼時(shí),要記住的第一個(gè)規(guī)則是在定義回調(diào)時(shí)不要濫用閉包。為回調(diào)創(chuàng)建命名函數(shù),避免使用閉包,并將中間結(jié)果作為參數(shù)傳遞。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書(shū)筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關(guān)注我的專欄,之后的博文將在專...

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

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

0條評(píng)論

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