APRIL 20, 2015
Events, Concurrency and JavaScript
Modern web apps are inherently event-driven yet much of the browser internals for triggering, executing, and handling events can seem as black box. Browsers model asynchronous I/O thru events and callbacks, enabling users to press keys and click mouses while XHR requests and timers trigger in code. Understanding how events work is critical for crafting high performance JavaScript. In this post we’ll focus on the browser’s built-in Web APIs, callback queues, event loops and JavaScript’s run-time.
Code in action. A button and event handler.
Do Stuff document.getElementById('doStuff') .addEventListener('click',function() { console.log('Do Stuff'); } );
Let’s trace a?Do Stuff?click event thru browser and describe the components along the way.


From Philip Robert’s diagram
Browser Runtime
User Interface?- User clicks the?Do Stuff?button. Simple enough.
Web APIs?- The?click?event propagates thru the DOM’s Web API triggering click handlers during the capture and bubble phases on parent and child elements. Web APIs are a multi-threaded area of the browser that allows many events to trigger at once. They become accessible to JavaScript code thru the familiar?window?object on page load. Examples beyond the DOM’s?document?are AJAX’sXMLHttpRequest, and timers?setTimeout()?function[1].
Event Queue?- Next the event’s callback is pushed into one of many event queues (also called task queues). Just as there are multiple Web APIs, browsers have event queues for things like network requests, DOM events, rendering, and more[2].
Event loop?- Then a single event loop chooses which callback to push onto the JavaScript call stack[3]. Here’s C++ pseudo code for Firefox’s event loop.
while(queue.waitForMessage()){ queue.processNextMessage(); }
[4]
Finally the event callback enters the JavaScript’s runtime within the browser.
JavaScript Runtime
The JavaScript engine has many components such as a parser for script loading, heap for object memory allocation, garbage collection system, interpreter, and more. Like other code event handlers execute on it’s call stack.
5.??Call Stack?- Every function invocation including event callbacks creates a new stack frame (also called execution object). These stack frames are pushed and popped from the top of the call stack, the top being the currently executing code[5]. When the function is returned it’s stack frame is popped from the stack.
Chrome’s V8 C++ source code of single stack frame:
/** * v8.h line 1372 -- A single JavaScript stack frame. */classV8_EXPORTStackFrame{public:intGetLineNumber()const;intGetColumn()const;intGetScriptId()const; LocalGetScriptName()const; LocalGetScriptNameOrSourceURL()const; LocalGetFunctionName()const;boolIsEval()const;boolIsConstructor()const; };
[6]
Three characteristics of JavaScript’s call stack.
Single threaded?- Threads are basic units of CPU utilization. As lower level OS constructs they consist of a thread ID, program counter, register set, and stack[7]. While the JavaScript engine itself is multi-threaded it’s call stack is single threaded allowing only one piece of code to execute at a time8.
Synchronous?- JavaScript call stack carries out tasks to completion instead of task switching and the same holds for events. This isn’t a requirement by the ECMAScript or WC3 specs. But there are some exceptions like?window.alert()?interrupts the current executing task.
Non-blocking?- Blocking occurs when the application state is suspended as a thread runs[7]. Browsers are non-blocking, still accepting events like mouse clicks even though they may not execute immediately.
CPU Intensive Tasks
CPU intensive tasks can be difficult because the single-threaded and synchronous run-time queues up other callbacks and threads into a wait state, e.g. the UI thread.
Let’s add a CPU intensive task.
Big Loop Do Stuff document.getElementById('bigLoop') .addEventListener('click',function() {// big loopfor(vararray = [], i = 0; i
Click?Big Loop?then?Do Stuff. When?Big Loop?handler runs the browser appears frozen. We know JavaScript’s call?stack is synchronous soBig Loopexecutes on the call stack until completion. It’s also non-blocking where?Do Stuff?clicks are still received even if they didn’t execute immediately.
Checkout this?CodePen?to see.
Solutions
1) Break the big loop into smaller loops and use?setTimeout()?on each loop.
... document.getElementById('bigLoop') .addEventListener('click',function() {vararray= []// smaller loopsetTimeout(function() {for(i = 0; i < 5000000; i++) {array.push(i); } }, 0);// smaller loopsetTimeout(function() {for(i = 0; i < 5000000; i++) {array.push(i); } }, 0); });
setTimeout()?executes in the WebAPI, then sends the callback to an event queue and allows the event loop to repaint before pushing it’s callback into the JavaScript call stack.
2) Use Web Workers, designed for CPU intensive tasks.
Summary
Events trigger in a multi-threaded area of the browser called?Web APIs. After an event (e.g. XHR request) completes, the Web API passes it’s callback to the?event queues. Next an?event loop?synchronously selects and pushes the event callback from the callback queues onto JavaScript’s single-threaded?call stack?to be executed. In short, events trigger asynchronously but their handlers execute on the call stack synchronously.
In this post we covered browser’s concurrency model for events including Web APIs, event queues, event loop, and JavaScript’s runtime. I’d enjoy questions or comments. Feel free to reach out via?Linkedin.
References
[1]?W3C Web APIs
[2]?W3C Event Queue (Task Queue)
[3]?W3C Event Loop
[4]?Concurrency modal and Event Loop, MDN
[5]?ECMAScript 10.3 Call Stack
[6] V8 source code include/v8.h line 1372.
[7] Silberschatz, Galvin, Gagne, Operating System Concepts 8th Ed. page 153, 570
[8] V8 source code src/x64/cpu-x64.cc
轉(zhuǎn)載地址:https://danmartensen.svbtle.com/events-concurrency-and-javascript
事件、并發(fā)和javascript
現(xiàn)代web應(yīng)用程序本質(zhì)上是事件驅(qū)動(dòng)的,大多數(shù)瀏覽器內(nèi)部對(duì)于觸發(fā)、執(zhí)行和處理事件都在一個(gè)黑盒子中。瀏覽器模型的異步I/O通過事件(events)和回調(diào)函數(shù)(callback)使用戶在按鍵和點(diǎn)擊鼠標(biāo)的同時(shí)也可以在代碼中觸發(fā)定時(shí)器和XHR(簡單講就是ajax請(qǐng)求)請(qǐng)求。了解事件的工作機(jī)制對(duì)于寫出高性能的javascript代碼至關(guān)重要。在這篇文章中,我們將重點(diǎn)介紹瀏覽器內(nèi)置Web API、隊(duì)列、事件輪詢和javascript運(yùn)行時(shí)。
例子如下:
Do Stuff document.getElementById('doStuff') .addEventListener('click',function() { console.log('Do Stuff'); } );
讓我們通過瀏覽器跟蹤一個(gè)Do Stuff點(diǎn)擊事件,并描述一路上的組件。


From Philip Robert’s diagram
Philip Robert的講解原文:https://vimeo.com/96425312
Browser Runtime
1.用戶界面:用戶點(diǎn)擊‘Do Stuff’按鈕。
2.Web APIs:點(diǎn)擊行為將DOM的Web API觸發(fā)的click handler(事件句柄)通過事件捕獲和事件冒泡傳遞給該元素的父元素和子元素,Web APIs這一模塊在瀏覽器中是多線程的,所以Web APIs允許多個(gè)事件在同一時(shí)間觸發(fā)。在頁面加載時(shí),他們通過window對(duì)象訪問javascript代碼,這樣的例子除了DOM的documen對(duì)象,還有AJAX的XMLHttpRequest對(duì)象和timers的setTimeout()函數(shù)等。
3.事件隊(duì)列
接下來,事件的回調(diào)函數(shù)被推到events queues(也叫作called task queues)中的一個(gè)里面。正如有多個(gè)Web API一樣,瀏覽器的事件隊(duì)列也包括networks事件隊(duì)列、DOM事件隊(duì)列和渲染事件隊(duì)列等。
4.事件輪詢(event loop)
然后單一的事件輪詢機(jī)制就會(huì)將事件隊(duì)列中處于隊(duì)列最前方的事件取出來推到j(luò)avascript調(diào)用棧中執(zhí)行。
JavaScript Runtime
Javascript引擎包含許多組件例如用于腳本加載的解析器、為對(duì)象分配內(nèi)存的堆、垃圾回收系統(tǒng)、解釋器等。
5.調(diào)用棧
每個(gè)函數(shù)包括事件回調(diào)函數(shù)在內(nèi)在調(diào)用時(shí)(推入調(diào)用棧的頂端)都會(huì)創(chuàng)建一個(gè)新的棧框架(也叫作執(zhí)行對(duì)象)。這些??蚣軓恼{(diào)用棧的棧頂被推入和彈出,棧頂是當(dāng)前所執(zhí)行的代碼。函數(shù)調(diào)用完畢該函數(shù)彈出調(diào)用棧。
Javascript調(diào)用棧的三個(gè)特性
1.單線程
線程是CPU利用率的基本單位,作為低級(jí)OS構(gòu)造他們由線程ID、程序計(jì)數(shù)器、寄存器和堆棧組成。雖然JavaScript引擎本身是多線程的,它的調(diào)用堆棧是單線程的,所以在同一時(shí)間內(nèi)只能執(zhí)行一段代碼(代碼只有被推入javascript調(diào)用棧才能被執(zhí)行,而調(diào)用棧是單線程的)。
2.同步
Javascript 調(diào)用棧執(zhí)行一個(gè)任務(wù)直到完成才會(huì)執(zhí)行下一個(gè)任務(wù),而不是在未完成的任務(wù)之間切換,事件也是一樣。 這不是ECMAScript或WC3規(guī)范的要求。 但有一些例外,如window.alert()中斷當(dāng)前正在執(zhí)行的任務(wù)。
3.非阻塞
當(dāng)應(yīng)用狀態(tài)是暫停時(shí),運(yùn)行的線程會(huì)被阻塞。瀏覽器是非阻塞的,它依舊會(huì)接收像鼠標(biāo)點(diǎn)擊這樣的事件即便他們不會(huì)立馬被執(zhí)行。
Summary
事件在稱為Web API的瀏覽器的多線程區(qū)域中觸發(fā)。 事件(例如XHR請(qǐng)求)完成后,Web API將其回調(diào)傳遞給事件隊(duì)列。 接下來,事件循環(huán)同步地選擇并將事件回調(diào)從回調(diào)隊(duì)列推送到JavaScript的單線程調(diào)用堆棧以被執(zhí)行。 簡而言之,事件觸發(fā)是異步的,但它們的處理程序在調(diào)用堆棧上同步執(zhí)行。
在這篇文章中,我們介紹了瀏覽器的并發(fā)模型,包括Web API,事件隊(duì)列,事件循環(huán)和JavaScript的運(yùn)行時(shí)。 我會(huì)喜歡問題或意見。 歡迎通過Linkedin與我們聯(lián)系。
References
[1]?W3C Web APIs
[2]?W3C Event Queue (Task Queue)
[3]?W3C Event Loop
[4]?Concurrency modal and Event Loop, MDN
[5]?ECMAScript 10.3 Call Stack
[6] V8 source code include/v8.h line 1372.
[7] Silberschatz, Galvin, Gagne, Operating System Concepts 8th Ed. page 153, 570
[8] V8 source code src/x64/cpu-x64.cc