摘要:中叫做調(diào)用棧先進(jìn)后出,后進(jìn)先出。如下圖這是典型的內(nèi)存溢出,可能會(huì)出現(xiàn)在某些場景下需要遞歸,但業(yè)務(wù)邏輯中的判斷又沒能正常計(jì)算進(jìn)入到預(yù)設(shè)情況,于是調(diào)用棧中不斷進(jìn)入,又無法執(zhí)行完,就造成內(nèi)存溢出了。
本文主要介紹Javascript事件循環(huán)在瀏覽器上的一些特性和應(yīng)用介紹。Javascript小知識(shí)
JavaScript的并發(fā)模型基于"事件循環(huán)"(Event Loop)。這個(gè)模型與像C或者Java這種其它語言中的模型截然不同。它永不阻塞,處理I/O通常通過事件和回調(diào)來執(zhí)行,所以當(dāng)一個(gè)應(yīng)用正等待IndexedDB查詢返回或者一個(gè)XHR請求返回時(shí),它仍然可以處理其它事情,如用戶輸入。【參:并發(fā)模型與事件循環(huán)】
需要了解的幾點(diǎn):單線程編程語言(Single Threaded)
只有一個(gè)主線程(one thread),并且只有一個(gè)調(diào)用棧(Call Stack),因此同一時(shí)間只能執(zhí)行同一件事情?!緟ⅲ篜hilip Roberts: What the heck is the event loop anyway? | JSConf EU (4:15)】
執(zhí)行上下文(Execution Context)
Javascript代碼執(zhí)行時(shí),會(huì)進(jìn)入一個(gè)執(zhí)行上下文。它可以理解為當(dāng)前代碼的運(yùn)行環(huán)境(包括三種:全局環(huán)境、函數(shù)環(huán)境、Eval環(huán)境)。【參:Javascript核心技術(shù)開發(fā)解密 Page-11】
糾正一點(diǎn)分享會(huì)可能存在的錯(cuò)誤,它和作用域(Scope)不同!作用域是針對變量的一個(gè)可訪問區(qū)域,而執(zhí)行上下文是屬于函數(shù)的指向的對象。(Scope pertains to the visibility of variables, and context refers to the object to which a function belongs.)【參:Why Should We Care About Scope and Context ?】
棧(stack)
函數(shù)調(diào)用形成了一個(gè)棧幀。JavaScript中叫做調(diào)用棧(Call Stack);先進(jìn)后出,后進(jìn)先出(LIFO)。
堆(heap)
對象被分配在一個(gè)堆中,即用以表示一個(gè)大部分非結(jié)構(gòu)化的內(nèi)存區(qū)域。
隊(duì)列(queue)
一個(gè)JavaScript運(yùn)行時(shí)包含了一個(gè)待處理的消息隊(duì)列。每一個(gè)消息都有一個(gè)為了處理這個(gè)消息相關(guān)聯(lián)的函數(shù)。
任務(wù)(Task)
主要是隊(duì)列中要執(zhí)行的函數(shù)。主要包含以下兩大類:
macrotask:包含執(zhí)行整體的js代碼,事件回調(diào),XHR回調(diào),定時(shí)器(setTimeout/setInterval/setImmediate),IO操作,UI render
microtask:更新應(yīng)用程序狀態(tài)的任務(wù),包括promise回調(diào),MutationObserver,process.nextTick,Object.observe
下圖展示了Event Loop的機(jī)制 這些代碼會(huì)發(fā)生什么? 基于JQuery的Ajax示例,如果沒有異步多么可怕!// This is assuming that you"re using jQuery jQuery.ajax({ url: "https://api.example.com/endpoint", success: function(response) { // This is your callback. }, async: false // And this is a terrible idea }); // 原文網(wǎng)址:https://itw01.com/2Z6WE2L.html
這里使用了JQuery的Ajax函數(shù),并為參數(shù)設(shè)置為同步執(zhí)行。那么將遇到一種可怕的情況,這段代碼在success回調(diào)前,后面的Javascript代碼將不再執(zhí)行。也就造成了可怕的阻塞(blocking)。
這段代碼什么鬼,看著有點(diǎn)暈XDlet bar = 0 function foo() { bar++ if (bar > 0) { return foo() } } foo()
沒錯(cuò),如果你不暈,說明你太棒了。這段代碼也會(huì)產(chǎn)生嚴(yán)重的問題。如下圖:
這是典型的內(nèi)存溢出,可能會(huì)出現(xiàn)在某些場景下需要遞歸,但業(yè)務(wù)邏輯中的判斷又沒能正常計(jì)算進(jìn)入到預(yù)設(shè)情況,于是調(diào)用棧中不斷進(jìn)入foo(),又無法執(zhí)行完,就造成內(nèi)存溢出了。
糾正一處分享會(huì)中的錯(cuò)誤,這個(gè)入棧過程沒有任何函數(shù)退出,所以會(huì)只進(jìn)不出,導(dǎo)致內(nèi)存爆炸。另外道哥提到的不斷累加到最大值為負(fù)數(shù)的情況,我測試了一下JS下,會(huì)變成Infinite。某些其他語言(例如:C)是會(huì)變成-1,和二進(jìn)制進(jìn)位有關(guān)。小測驗(yàn):Demo - 1
setTimeout(() => { console.log(1); }, 0); console.log(2); for (let i = 0; i < 3; i++) { console.log(i); } console.log(4);
輸出結(jié)果:
A: 1, 2, 0, 1, 2, 4
B: 2, 4, 0, 1, 2, 1
C: 2, 0, 1, 2, 4, 1
D: 2, 4, 0, 1, 2, 1
console.log(1); for (let i = 0; i < 3; i++) { setTimeout(() => { console.log("2-" + i); }, 0); } console.log(3);
輸出結(jié)果:
A: 1, 2-2, 2-2, 2-2, 3
B: 1, 3, 2-2, 2-2, 2-2
C: 1, 2-0, 2-1, 2-2, 3
D: 1, 3, 2-0, 2-1, 2-2
我想大家應(yīng)該都正確答出來了吧:D,接下來我將詳細(xì)分析一些示例,以便于理解事件循環(huán)。
事件循環(huán)流程分析 示例分析1:這里我借用了作者稀土掘金的深入理解事件迴圈和非同步流程控制文中的一段示范。
console.log("Hi") setTimeout(function cb1() { console.log("cb1") }, 5000) console.log("Bye")
不論是否懂得事件循環(huán)的初學(xué)者,看到這段代碼應(yīng)該也能猜出來答案是: Hi Bye cb1。畢竟cb1有一個(gè)5s的定時(shí)器。但是執(zhí)行細(xì)節(jié)是怎樣的呢。我們來看下面這張gif圖。
圖中已經(jīng)很清楚的展示了整個(gè)Javascript代碼是如何運(yùn)作的。相信大家已經(jīng)有較大的收獲了。
示例分析2:我們來看這個(gè)頁面中的Javascript部分:
function one() { throw new Error("Oops!") } function two() { one() } function three() { two() } three()
我們在瀏覽器端執(zhí)行時(shí),打個(gè)斷點(diǎn)在throw new Error("Oops!")這一行。如下圖:
在了解了事件循環(huán)的執(zhí)行順序后,我們可以輕松知道他的執(zhí)行順序,通過Chrome開發(fā)者工具、我們觀察圖中Call Stack區(qū)域,箭頭指向的one也正是我們斷點(diǎn)的地方,下面依次是two、three、(anonymous),這個(gè)是完全符合棧的先進(jìn)后出,后進(jìn)先出(last-in-first-out)的特征~
我們在實(shí)際開發(fā)中,也可以通過Call Stack里面觀察,找出上一層入口,分析異常原因。會(huì)有很大的幫助呢~
接著關(guān)閉斷點(diǎn)繼續(xù)執(zhí)行,瀏覽器會(huì)拋出錯(cuò)誤,錯(cuò)誤信息如下,也是符合棧特點(diǎn)的
其他該文章中有部分內(nèi)容在我制作的PPT中并未體現(xiàn)出來,對于這次分享會(huì),我對Javascript一些運(yùn)行機(jī)制有更深的理解,由于時(shí)間倉促也就在本次分享做了一點(diǎn)入門介紹。下期我將會(huì)結(jié)合更多的示例,對Node.js的事件循環(huán)與瀏覽器端的差異等等進(jìn)行更深入的介紹,當(dāng)大家都有所收獲后,就大可忘記了。
文中參考的一些資料(★表示推薦等級(jí))深入理解js事件循環(huán)機(jī)制(瀏覽器篇) ★★★★☆
深入理解事件迴圈和非同步流程控制 ★★★★☆
Philip Roberts: What the heck is the event loop anyway? | JSConf EU ★★★★★
這是一段來自Youtube的演講視頻,視頻中有用到一個(gè)工具"loupe - 模擬執(zhí)行順序的工具",值得研究! ★★★★★
Tasks, microtasks, queues and schedules ★★★★★
文中有例子通過動(dòng)畫來展示執(zhí)行順序問題感覺超級(jí)棒!也對不通瀏覽器的結(jié)果有做分析,當(dāng)然也許部分內(nèi)容有些不一致,需要注意。
[The JavaScript Event Loop [Presentation]](https://thomashunter.name/pos...
文中所提到的參考內(nèi)容及使用到的PPT資料(有驚喜)JS事件循環(huán)PPT - Whidy
JavaScript Event Loop - Thomas Hunter
JS Event Loop - Sonle
All you need to know about the JavaScript event loop - @sasatatar & @codaxy
全部整包下載
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/99017.html
摘要:標(biāo)準(zhǔn)庫中的所有方法都提供非阻塞的異步版本,并接受回調(diào)函數(shù),某些方法還具有對應(yīng)的阻塞方法,其名稱以結(jié)尾。比較代碼阻塞方法同步執(zhí)行,非阻塞方法異步執(zhí)行。 阻塞與非阻塞概述 此概述介紹了Node.js中阻塞與非阻塞調(diào)用之間的區(qū)別,此概述將引用事件循環(huán)和libuv,但不需要事先了解這些主題,假設(shè)讀者對JavaScript語言和Node.js回調(diào)模式有基本的了解。 I/O主要指與libuv支持的...
摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
閱讀 2388·2021-11-24 09:38
閱讀 3487·2021-11-22 14:44
閱讀 1221·2021-07-29 13:48
閱讀 2686·2019-08-29 13:20
閱讀 1181·2019-08-29 11:08
閱讀 2156·2019-08-26 10:58
閱讀 1327·2019-08-26 10:55
閱讀 3219·2019-08-26 10:39