javascript事件的异步机制

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

转载地址:https://danmartensen.svbtle.com/events-concurrency-and-javascript

事件、并发和javascript

现代web应用程序本质上是事件驱动的,大多数浏览器内部对于触发、执行和处理事件都在一个黑盒子中。浏览器模型的异步I/O通过事件(events)和回调函数(callback)使用户在按键和点击鼠标的同时也可以在代码中触发定时器和XHR(简单讲就是ajax请求)请求。了解事件的工作机制对于写出高性能的javascript代码至关重要。在这篇文章中,我们将重点介绍浏览器内置Web API、队列、事件轮询和javascript运行时。

例子如下:

Do Stuff document.getElementById('doStuff') .addEventListener('click',function() { console.log('Do Stuff'); } );

让我们通过浏览器跟踪一个Do Stuff点击事件,并描述一路上的组件。


From Philip Robert’s diagram

Philip Robert的讲解原文:https://vimeo.com/96425312

Browser Runtime

1.用户界面:用户点击‘Do Stuff’按钮。

2.Web APIs:点击行为将DOM的Web API触发的click handler(事件句柄)通过事件捕获和事件冒泡传递给该元素的父元素和子元素,Web APIs这一模块在浏览器中是多线程的,所以Web APIs允许多个事件在同一时间触发。在页面加载时,他们通过window对象访问javascript代码,这样的例子除了DOM的documen对象,还有AJAX的XMLHttpRequest对象和timers的setTimeout()函数等。

3.事件队列

接下来,事件的回调函数被推到events queues(也叫作called task queues)中的一个里面。正如有多个Web API一样,浏览器的事件队列也包括networks事件队列、DOM事件队列和渲染事件队列等。

4.事件轮询(event loop)

然后单一的事件轮询机制就会将事件队列中处于队列最前方的事件取出来推到javascript调用栈中执行。

JavaScript Runtime

Javascript引擎包含许多组件例如用于脚本加载的解析器、为对象分配内存的堆、垃圾回收系统、解释器等。

5.调用栈

每个函数包括事件回调函数在内在调用时(推入调用栈的顶端)都会创建一个新的栈框架(也叫作执行对象)。这些栈框架从调用栈的栈顶被推入和弹出,栈顶是当前所执行的代码。函数调用完毕该函数弹出调用栈。

Javascript调用栈的三个特性

1.单线程

线程是CPU利用率的基本单位,作为低级OS构造他们由线程ID、程序计数器、寄存器和堆栈组成。虽然JavaScript引擎本身是多线程的,它的调用堆栈是单线程的,所以在同一时间内只能执行一段代码(代码只有被推入javascript调用栈才能被执行,而调用栈是单线程的)。

2.同步

Javascript 调用栈执行一个任务直到完成才会执行下一个任务,而不是在未完成的任务之间切换,事件也是一样。 这不是ECMAScript或WC3规范的要求。 但有一些例外,如window.alert()中断当前正在执行的任务。

3.非阻塞

当应用状态是暂停时,运行的线程会被阻塞。浏览器是非阻塞的,它依旧会接收像鼠标点击这样的事件即便他们不会立马被执行。

Summary

事件在称为Web API的浏览器的多线程区域中触发。 事件(例如XHR请求)完成后,Web API将其回调传递给事件队列。 接下来,事件循环同步地选择并将事件回调从回调队列推送到JavaScript的单线程调用堆栈以被执行。 简而言之,事件触发是异步的,但它们的处理程序在调用堆栈上同步执行。

在这篇文章中,我们介绍了浏览器的并发模型,包括Web API,事件队列,事件循环和JavaScript的运行时。 我会喜欢问题或意见。 欢迎通过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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容

  • PLEASE READ THE FOLLOWING APPLE DEVELOPER PROGRAM LICENSE...
    念念不忘的阅读 13,413评论 5 6
  • title标题: A Web Crawler With asyncio Coroutinesauthor作者: A...
    彰乐乐乐乐阅读 2,017评论 0 8
  • 今天早上起来先热了昨天弄得包子,吃完把日常锻炼做了,接着在英语流利说打了今天的卡,这个app还是偏向口语的练习,所...
    老谋阅读 115评论 1 0
  • 曾经梦见鱼在水的下面 哭泣,我为什么看不到我的影子 泪水让湖水涨了一毫米 只是咸,嘴唇拼命的干涩 我往湖里丢了那么...
    浮阿屠阅读 144评论 0 0
  • 爷爷在小松树义务工作,不知不觉已经三年了。这三年来,很多地方都能看到爷爷的身影,从最初的修修补补,到后来的...
    茉莉莉茉阅读 654评论 0 0