JavaScript中的JS引擎的執(zhí)行機(jī)制

一. JavaScript是單線程的

為什么呢 ? 首先JavaScript語言的一大特點就是單線程, 通俗點說就是, 同一個時間只能做一件事.那么會又有新的問題, JavaScript為什么不能有多個線程呢 ?

JavaScript最初被設(shè)計用在瀏覽器中, 那么設(shè)想下, 瀏覽器中的JavaScript是多線程的.例如 : 假定JavaScript同時有兩個線程, 一個線程在某個DOM節(jié)點上添加內(nèi)容, 另外一個線程刪除了這個節(jié)點, 這時瀏覽器應(yīng)該以哪個線程為準(zhǔn)呢 ?

后來, HTML5提出Web Worker標(biāo)準(zhǔn), 允許JavaScript腳本創(chuàng)建多個線程, 但是子線程完全受主線程控制, 且不得操作DOM, 所以, 這個新標(biāo)準(zhǔn)并沒有改變JavaScript單線程本質(zhì)

二. JavaScript為什么需要異步 ?

如果JavaScript中不存在異步, 只能自上而下執(zhí)行, 如果上一行解析時間很長, 那么下面的代碼就會被阻塞.對于用戶而言, 阻塞就意味著 "卡死", 這樣就導(dǎo)致了很差的用戶體驗.所以, JavaScript中存在異步執(zhí)行

三. 那么又是如何實現(xiàn)異步的呢 ?

任務(wù)隊列 :1. 所有同步任務(wù)都在主線程上執(zhí)行, 形成一個執(zhí)行棧(stack)暖璧。2.主線程之外, 還存在一個任務(wù)隊列Event Loop, 異步任務(wù)在event table中注冊函數(shù), 當(dāng)滿足觸發(fā)條件(即DOM,AJAX夜惭,setTimeout水醋,setImmediate有返回結(jié)果了) 后, 被推入任務(wù)隊列(Event Loop)葛碧。3. 一旦執(zhí)行棧(stack) 中所有同步任務(wù)都執(zhí)行完了, 系統(tǒng)就會讀取任務(wù)隊列(Event Loop), 看看里面有哪些事件.那些對應(yīng)的異步任務(wù), 于是結(jié)束等待狀態(tài), 進(jìn)入執(zhí)行棧, 開始執(zhí)行 。4.主線程不斷重復(fù)上面的第三步粪狼。
例子1:

console.log(1)
setTimeout(
    function() {
        console.log(2)
    },
    0)
console.log(3)

運(yùn)行的結(jié)果是:1,3任岸,2
代碼分析:
1.console.log(1)是同步任務(wù)再榄,放入主線程里
2.setTimeout是異步任務(wù),被放入event table享潜,0秒之后被推入任務(wù)隊列(Event Loop)里
3.console.log(3)是同步任務(wù)困鸥,放到主線程里.
當(dāng)1,3在控制臺被打印后剑按,主線程去Event Loop(事件隊列)里查看是否有可執(zhí)行的函數(shù)疾就,執(zhí)行setTimeout里的函數(shù),這就是Event Loop

四.Event Loop是什么 ?

主線程從任務(wù)隊列(Event Loop) 中讀取事件, 這個過程是循環(huán)不斷的, 所以整個的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán)).


1.png

上圖中, 主線程運(yùn)行的時候, 產(chǎn)生堆(heap) 和棧(stack), 棧中的代碼調(diào)用各種外部API艺蝴, 它們在” 任務(wù)隊列(Event Loop)” 中加入各種事件( click虐译, load, done)吴趴。 只要棧中的代碼執(zhí)行完畢漆诽, 主線程就會去讀取” 任務(wù)隊列(Event Loop)”侮攀, 依次執(zhí)行那些事件所對應(yīng)的回調(diào)函數(shù)。
例子2:

setTimeout(function() {
    console.log('定時器開始啦')
});
new Promise(function(resolve) {
    console.log('馬上執(zhí)行for循環(huán)啦');

    for(var i =0; i <10000; i++) {
        i ==99 &&resolve();
    }
}).then(function() {
    console.log('執(zhí)行then函數(shù)啦')
});
console.log('代碼執(zhí)行結(jié)束');

嘗試按照厢拭,上文我們剛學(xué)到的js執(zhí)行機(jī)制去分析:
1.setTimeout 是異步任務(wù)兰英,被放到event table
2.new Promise是同步任務(wù),被放到主線程里供鸠,直接執(zhí)行打印console.log('馬上執(zhí)行for循環(huán)啦');
3..then里的函數(shù)是異步任務(wù)畦贸,被放到event table
4.console.log('代碼執(zhí)行結(jié)束');是同步代碼,被放到主線程里楞捂,直接執(zhí)行
所以根據(jù)分析的結(jié)果是:馬上執(zhí)行for循環(huán)啦---代碼執(zhí)行結(jié)束---定時器開始啦---執(zhí)行then函數(shù)啦
自己運(yùn)行了下代碼后薄坏,結(jié)果居然不是這樣的,而是:
馬上執(zhí)行for循環(huán)啦---代碼執(zhí)行結(jié)束---執(zhí)行then函數(shù)啦---定時器開始啦

事實上寨闹,按照異步和同步的方式來劃分胶坠,并不準(zhǔn)確,而準(zhǔn)確的劃分方式是:
macro-task(宏任務(wù)):script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering繁堡。
micro-task(微任務(wù)):process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)


2.jpg

按照這種分類方式沈善,js的執(zhí)行機(jī)制是:
1.執(zhí)行一個宏任務(wù),過程中如果遇到微任務(wù)椭蹄,就將其放到微任務(wù)的"事件隊列"里
2.當(dāng)前宏任務(wù)執(zhí)行完成后闻牡,會查看微任務(wù)的"事件隊列",并將里面全部的微任務(wù)依次執(zhí)行完
3.重復(fù)以上2步驟绳矩,結(jié)合圖1和圖2就是更為準(zhǔn)確的js執(zhí)行機(jī)制了
那么罩润,去分析例2:
1.首先執(zhí)行script下的宏任務(wù),遇到setTimeout,將其放到宏任務(wù)的“隊列”里
2.遇到 new Promise直接執(zhí)行翼馆,打印"馬上執(zhí)行for循環(huán)啦"
3.遇到then方法割以,是微任務(wù),將其放到微任務(wù)的“隊列”里写妥。
4.遇到console.log('代碼執(zhí)行結(jié)束');是同步任務(wù)拳球,直接打印"代碼執(zhí)行結(jié)束"
5.本輪宏任務(wù)執(zhí)行完畢,查看本輪的微任務(wù)珍特,發(fā)現(xiàn)有一個then方法里的函數(shù)祝峻,打印"執(zhí)行then函數(shù)啦"
6.到此,本輪的event loop 全部完成。
7.下一輪的循環(huán)里扎筒,先執(zhí)行一個宏任務(wù)莱找,發(fā)現(xiàn)宏任務(wù)的“隊列”里有一個setTimeout里的函數(shù),執(zhí)行打印"定時器開始啦"
所以最后的執(zhí)行順序是: 馬上執(zhí)行for循環(huán)啦---代碼執(zhí)行結(jié)束---執(zhí)行then函數(shù)啦---定時器開始啦

五.定時器setTimeout()和setInterval()

定時器指定某些代碼在多少時間之后執(zhí)行這叫做”定時器”(timer)功能,也就是定時執(zhí)行的代碼嗜桌。
例子3:

setTimeout(function(){
    console.log('執(zhí)行了')
},3000)

我們一般會說:3秒后奥溺,會執(zhí)行setTimeout里的那個函數(shù),但是這種說法并不嚴(yán)謹(jǐn)骨宠,準(zhǔn)確的解釋是:3秒后浮定,setTimeout里的函數(shù)會被推入事件隊列(Event Loop)相满,而事件隊列(Event Loop)里的任務(wù),只有在主線程空閑時才會執(zhí)行桦卒,所以條件只有同時滿足(ps:3秒后并且主線程空閑)時立美,才會3秒后執(zhí)行函數(shù)

如果主線程執(zhí)行內(nèi)容很多,執(zhí)行時間超過3秒方灾,比如主線程里執(zhí)行棧執(zhí)行了10秒建蹄,那么這個函數(shù)只能10秒后執(zhí)行了

六.Node.js的Event Loop

Node.js也是單線程的Event Loop,但是它的運(yùn)行機(jī)制不同于瀏覽器環(huán)境裕偿。


3.png

如圖所示洞慎,Node.js的運(yùn)行機(jī)制如下:
1.V8引擎解析JavaScript腳本。
2.解析后的代碼嘿棘,調(diào)用Node API劲腿。
3.libuv庫負(fù)責(zé)Node API的執(zhí)行。它將不同的任務(wù)分配給不同的線程蔫巩,形成一個Event Loop(事件循環(huán))谆棱,以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給V8引擎快压。
4.V8引擎再將結(jié)果返回給用戶圆仔。

除了setTimeout和setInterval這兩個方法,Node.js還提供了另外兩個與”任務(wù)隊列”有關(guān)的方法:process.nextTick和setImmediate蔫劣。它們可以幫助我們加深對”任務(wù)隊列”的理解坪郭。
nextTick setImmediate 區(qū)別和聯(lián)系
nextTick :把回調(diào)函數(shù)放在當(dāng)前執(zhí)行棧的底部,而多個process.nextTick語句總是在當(dāng)前”執(zhí)行棧”一次執(zhí)行完
setImmediate :把回調(diào)函數(shù)放在事件隊列(event loop)的尾部,而多個setImmediate可能則需要多次loop才能執(zhí)行完

例子4:

process.nextTick(function A() {
  console.log(1);
  process.nextTick(function B(){console.log(2);});
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)

上面代碼中脉幢,由于process.nextTick方法指定的回調(diào)函數(shù)歪沃,總是在當(dāng)前”執(zhí)行棧”的尾部觸發(fā)嫌松,所以不僅回調(diào)函數(shù)A比setTimeout指定的回調(diào)函數(shù)timeout先執(zhí)行沪曙,而且函數(shù)B也比timeout先執(zhí)行。這說明萎羔,如果有多個process.nextTick語句(不管它們是否嵌套)液走,將全部在當(dāng)前”執(zhí)行棧”執(zhí)行贾陷。所以結(jié)果是:1缘眶,2,'TIMEOUT FIRED'

現(xiàn)在髓废,再來看看setImmediate
例子5:

setImmediate(function A() {
  console.log(1);
  setImmediate(function B(){
    console.log(2);
  });
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0);

上面代碼中巷懈,setImmediate與setTimeout(fn,0)各自添加了一個回調(diào)函數(shù)A和timeout,都是在下一次事件隊列(Event Loop)觸發(fā)慌洪。那么顶燕,哪個回調(diào)函數(shù)先執(zhí)行呢凑保?答案是不確定。運(yùn)行結(jié)果可能是1,TIMEOUT FIRED,2涌攻,也可能是TIMEOUT FIRED,1,2愉适。

令人困惑的是,Node.js文檔中稱癣漆,setImmediate指定的回調(diào)函數(shù)维咸,總是排在setTimeout前面。實際上惠爽,這種情況只發(fā)生在遞歸調(diào)用的時候癌蓖。

例子6:

setImmediate(function (){
  setImmediate(function A() {
    console.log(1);
    setImmediate(function B(){console.log(2);});
  });

  setTimeout(function timeout() {
    console.log('TIMEOUT FIRED');
  }, 0);
});

上面代碼中,setImmediate和setTimeout被封裝在一個setImmediate里面婚肆,它的運(yùn)行結(jié)果總是1租副,TIMEOUT FIRED,2较性,這時函數(shù)A一定在timeout前面觸發(fā)用僧。至于2排在TIMEOUT FIRED的后面(即函數(shù)B在timeout后面觸發(fā)),是因為setImmediate總是將事件注冊到下一輪事件隊列(Event Loop)赞咙,所以函數(shù)A和timeout是在同一輪Loop執(zhí)行责循,而函數(shù)B在下一輪Loop執(zhí)行

另外,由于process.nextTick指定的回調(diào)函數(shù)是在本次”事件循環(huán)”觸發(fā)攀操,而setImmediate指定的是在下次”事件循環(huán)”觸發(fā)院仿,所以很顯然,前者總是比后者發(fā)生得早速和,而且執(zhí)行效率也高(因為不用檢查”任務(wù)隊列”)歹垫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羹与,一起剝皮案震驚了整個濱河市荞雏,隨后出現(xiàn)的幾起案子耘斩,更是在濱河造成了極大的恐慌憨降,老刑警劉巖趣效,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈涮,死亡現(xiàn)場離奇詭異奈应,居然都是意外死亡浩销,警方通過查閱死者的電腦和手機(jī)痒留,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門谴麦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伸头,你說我怎么就攤上這事匾效。” “怎么了恤磷?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵面哼,是天一觀的道長野宜。 經(jīng)常有香客問我,道長魔策,這世上最難降的妖魔是什么匈子? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮闯袒,結(jié)果婚禮上虎敦,老公的妹妹穿的比我還像新娘。我一直安慰自己政敢,他們只是感情好其徙,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喷户,像睡著了一般唾那。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褪尝,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天闹获,我揣著相機(jī)與錄音,去河邊找鬼河哑。 笑死避诽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灾馒。 我是一名探鬼主播茎用,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼遣总,長吁一口氣:“原來是場噩夢啊……” “哼睬罗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旭斥,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤容达,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垂券,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體花盐,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年菇爪,在試婚紗的時候發(fā)現(xiàn)自己被綠了算芯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凳宙,死狀恐怖熙揍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氏涩,我是刑警寧澤届囚,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布有梆,位于F島的核電站,受9級特大地震影響意系,放射性物質(zhì)發(fā)生泄漏泥耀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一蛔添、第九天 我趴在偏房一處隱蔽的房頂上張望痰催。 院中可真熱鬧,春花似錦迎瞧、人聲如沸陨囊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘醋。三九已至,卻和暖如春咏尝,著一層夾襖步出監(jiān)牢的瞬間压语,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工编检, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留胎食,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓允懂,卻偏偏與公主長得像厕怜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蕾总,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容