最近本人對于js的運(yùn)行機(jī)制灶壶,特別是異步,還有回調(diào)函數(shù)感覺很亂杈曲,于是參考了很多有用的博客(博客原文地址會在文末給出)驰凛,整理如下:
js單線程
我們都知道,Javascript語言的執(zhí)行環(huán)境是"單線程"(single thread)担扑。也就是說恰响,瀏覽器只分配給js一個(gè)主線程用來執(zhí)行任務(wù)即函數(shù),但是每次只能執(zhí)行一個(gè)任務(wù)涌献,只有等到當(dāng)前任務(wù)執(zhí)行完成后胚宦,才執(zhí)行后面的任務(wù),這些任務(wù)形成一個(gè)任務(wù)隊(duì)列排隊(duì)等候執(zhí)行燕垃,這一點(diǎn)和我們?nèi)粘5呐抨?duì)很像枢劝,譬如排隊(duì)買奶茶,只有等到前面一個(gè)人買完奶茶付完錢卜壕,排在他后面的人才可以買奶茶呈野。但是,當(dāng)前面一個(gè)任務(wù)很耗時(shí)時(shí)印叁,后面的任務(wù)就不得不等著被冒,這時(shí)候整個(gè)程序的執(zhí)行效率就會下降,就像我們平時(shí)遇到的瀏覽器無響應(yīng)即頁面假死往往是因?yàn)槟扯蝚s代碼長時(shí)間運(yùn)行如死循環(huán)轮蜕,導(dǎo)致頁面卡死昨悼,后面的任務(wù)無法執(zhí)行。
講到j(luò)s的單線程跃洛,就不得不來了解一下瀏覽器
瀏覽器多線程
如圖率触,瀏覽器是一個(gè)多線程的執(zhí)行環(huán)境,在瀏覽器的內(nèi)核中分配了多個(gè)線程汇竭,其中瀏覽器常駐三大線程: js引擎線程葱蝗,GUI渲染線程穴张,瀏覽器事件觸發(fā)線程。最主要的線程之一即是js引擎的線程两曼。由于這三個(gè)線程同時(shí)要訪問DOM樹皂甘,所以為了線程安全,瀏覽器內(nèi)部需要做互斥即當(dāng)JS引擎在執(zhí)行代碼的時(shí)候悼凑,界面渲染和事件響應(yīng)兩個(gè)線程是被暫停的偿枕。而所以當(dāng)JS出現(xiàn)死循環(huán),瀏覽器無法響應(yīng)點(diǎn)擊户辫,也無法更新界面渐夸。
前面說到,前端會有一些任務(wù)十分耗時(shí)渔欢,而由于js是單線程使用會降低執(zhí)行效率墓塌,這些耗時(shí)的任務(wù)如網(wǎng)絡(luò)請求,定時(shí)器和事件監(jiān)聽奥额。所以桃纯,瀏覽器為這些耗時(shí)任務(wù)開辟了另外的線程,主要包括http請求線程披坏,瀏覽器定時(shí)觸發(fā)器态坦,瀏覽器事件觸發(fā)線程,這些任務(wù)是異步的(見圖)棒拂。(詳細(xì)過程見此博文伞梯,博主講得很好~ 個(gè)人建議必須看一看哦~)
任務(wù)隊(duì)列
說回剛剛的js單線程,為了不讓前面的耗時(shí)任務(wù)導(dǎo)致的問題出現(xiàn)帚屉,js的設(shè)計(jì)者把js的任務(wù)分為同步任務(wù)和異步任務(wù)谜诫。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù)攻旦,只有前一個(gè)任務(wù)執(zhí)行完畢喻旷,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是牢屋,不進(jìn)入主線程且预、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程烙无,某個(gè)異步任務(wù)可以執(zhí)行了锋谐,該任務(wù)才會進(jìn)入主線程執(zhí)行。如我們剛剛所講到的瀏覽器為網(wǎng)絡(luò)請求開辟的http請求線程就是異步任務(wù)截酷。
具體來說涮拗,異步執(zhí)行的運(yùn)行機(jī)制如下。(同步執(zhí)行也是如此,因?yàn)樗梢员灰暈闆]有異步任務(wù)的異步執(zhí)行三热。)
(1)所有同步任務(wù)都在主線程上執(zhí)行鼓择,形成一個(gè)[執(zhí)行棧]。
(2)主線程之外就漾,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)呐能。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件从藤。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢催跪,系統(tǒng)就會讀取"任務(wù)隊(duì)列"锁蠕,看看里面有哪些事件夷野。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài)荣倾,進(jìn)入執(zhí)行棧悯搔,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步舌仍。
(參考與阮一峰老師的博文JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop)
回調(diào)函數(shù)
有了以上了解我們可以知道妒貌,主線程內(nèi)的同步任務(wù)執(zhí)行完畢后,就會執(zhí)行排在任務(wù)隊(duì)列第一位的異步任務(wù)铸豁,這個(gè)過程不斷重復(fù)灌曙。
當(dāng)主線程開始執(zhí)行異步任務(wù),實(shí)際就是執(zhí)行對應(yīng)的回調(diào)函數(shù)节芥。
我們來看一下例子:
setTimeout(function(){
console.log('Hello');
}在刺,10);
執(zhí)行這段代碼,瀏覽器異步執(zhí)行計(jì)時(shí)操作(注意這里的瀏覽器模型定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的)头镊,當(dāng)10ms到了之后蚣驼,就會觸發(fā)定時(shí)事件,這時(shí)就會把其中的回調(diào)函數(shù)放到任務(wù)隊(duì)列中相艇,所以當(dāng)主線程空閑時(shí)在任務(wù)隊(duì)列中“讀取”并且執(zhí)行的就是回調(diào)函數(shù)颖杏。
異步任務(wù)必須指定回調(diào)函數(shù)。
Event Loop
主線程從"任務(wù)隊(duì)列"中讀取事件坛芽,這個(gè)過程是循環(huán)不斷的留储,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))。
如圖咙轩,WebAPIs就是js線程外部的api如我們剛剛所說瀏覽器為異步任務(wù)所開辟的線程欲鹏。而任務(wù)隊(duì)列就是callbackqueue,我們知道任務(wù)隊(duì)列中其實(shí)就是各種異步任務(wù)的回調(diào)函數(shù)臭墨,從callbackqueue的直譯“回調(diào)隊(duì)列”也可看出赔嚎。而heap堆和stack棧組成了js的主線程,當(dāng)stack中的函數(shù)執(zhí)行完成后,就會在callbackqueue中尋找下一個(gè)任務(wù)并把它推入棧尤误,這個(gè)尋找的過程就叫event loop(事件循環(huán))侠畔。
看了上面你是不是對js的運(yùn)行機(jī)制有了了解呢~
我們把學(xué)會的知識來用一用:
js中的異步之定時(shí)器
說起js的異步,很多人第一反應(yīng)是Ajax损晤,但其實(shí)js中最基礎(chǔ)的異步就是setTimeout/setInterval软棺。(小伙伴可不要把定時(shí)器忘了哦:))
我們以setTimeout為例,setTimeout接受兩個(gè)參數(shù)尤勋,第一個(gè)是回調(diào)函數(shù)喘落,第二個(gè)是推遲執(zhí)行的毫秒數(shù)。
我們看看例子:
setTimeout(function(){
console.log(0);
},0)
console.log(1);
// 1
// 0
是不是以為打印的順序是0,1最冰?但大家注意哦瘦棋,這時(shí)候?yàn)g覽器打印的順序是1,0暖哨。大家可能疑問了赌朋,setTimeout中設(shè)置的推遲執(zhí)行的毫秒數(shù)是0呀,不就是立即執(zhí)行的意思嗎篇裁。大家還記得剛剛我們說了當(dāng)有耗時(shí)任務(wù)時(shí)沛慢,會把它放在任務(wù)隊(duì)列中等待主線程空閑然后再執(zhí)行,實(shí)際在執(zhí)行程序的時(shí)候达布,瀏覽器會默認(rèn)setTimeout以及ajax請求這一類的方法都是耗時(shí)程序(盡管可能不耗時(shí))团甲,也就是上面說過的瀏覽器會為其異步開辟線程。所以此時(shí)的setTimeout盡管它推遲時(shí)間為0黍聂,但是js不會立即執(zhí)行躺苦,而是把它加入任務(wù)隊(duì)列,當(dāng)執(zhí)行完執(zhí)行棧的同步任務(wù)也就是打印1后分冈,再執(zhí)行setTimeout的回調(diào)函數(shù)圾另,打印0。
總之雕沉,setTimeout(fn,0)的含義是集乔,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,也就是說坡椒,盡可能早得執(zhí)行扰路。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完倔叼,才會得到執(zhí)行汗唱。
所以注意的是,setTimeout()只是將事件插入了任務(wù)隊(duì)列丈攒,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完哩罪,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)授霸。但如果當(dāng)前任務(wù)十分耗時(shí),需要等很久际插,所以并沒有辦法保證碘耳,回調(diào)函數(shù)一定會在setTimeout()指定的時(shí)間執(zhí)行,比如說你指定10ms后執(zhí)行框弛,但是當(dāng)前的任務(wù)執(zhí)行了20ms辛辨,所以setTimeout的回調(diào)函數(shù)并不能在10ms后立即執(zhí)行,可能要20ms后瑟枫,如果setTimeout在任務(wù)隊(duì)列中不是排第一位斗搞,可能還不止20ms。
js異步編程的方法
這個(gè)我還不是很懂~大家可以參考阮一峰老師的Javascript異步編程的4種方法
希望一包的文章可以幫到你們~
參考文章:
http://blog.csdn.net/qq_22855325/article/details/72958345(贊~)
https://www.cnblogs.com/woodyblog/p/6061671.html(重點(diǎn)參考這篇文章博主寫得很好~)
http://blog.csdn.net/kfanning/article/details/5768776(贊~)
http://www.ruanyifeng.com/blog/2014/10/event-loop.html(阮一峰老師嘛~)
http://www.cnblogs.com/smght/p/4368399.html#3575993(贊~)