aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/eventloop
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:40:17 -0500
commit33058f2b292b3a581333bdfb21b8f671898c5060 (patch)
tree51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/javascript/eventloop
parent8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff)
downloadtranslated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2
translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip
initial commit
Diffstat (limited to 'files/zh-cn/web/javascript/eventloop')
-rw-r--r--files/zh-cn/web/javascript/eventloop/index.html159
1 files changed, 159 insertions, 0 deletions
diff --git a/files/zh-cn/web/javascript/eventloop/index.html b/files/zh-cn/web/javascript/eventloop/index.html
new file mode 100644
index 0000000000..3decb3824e
--- /dev/null
+++ b/files/zh-cn/web/javascript/eventloop/index.html
@@ -0,0 +1,159 @@
+---
+title: 并发模型与事件循环
+slug: Web/JavaScript/EventLoop
+tags:
+ - JavaScript
+ - 事件
+ - 事件处理
+ - 事件循环
+ - 事件管理
+ - 事件队列
+ - 进阶
+translation_of: Web/JavaScript/EventLoop
+---
+<div>{{JsSidebar("Advanced")}}</div>
+
+<p><span class="seoSummary">JavaScript有一个基于<strong>事件循环</strong>的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。</span>这个模型与其它语言中的模型截然不同,比如 C 和 Java。</p>
+
+<h2 id="运行时概念">运行时概念</h2>
+
+<p>接下来的内容解释了这个理论模型。现代JavaScript引擎实现并着重优化了以下描述的这些语义。</p>
+
+<h3 id="可视化描述">可视化描述</h3>
+
+<p style="text-align: center;"><img alt="Stack, heap, queue" src="https://mdn.mozillademos.org/files/17124/The_Javascript_Runtime_Environment_Example.svg" style="height: 271px; width: 295px;"></p>
+
+<h3 id="栈">栈</h3>
+
+<p>函数调用形成了一个由若干帧组成的栈。</p>
+
+<pre class="brush: js notranslate">function foo(b) {
+ let a = 10;
+ return a + b + 11;
+}
+
+function bar(x) {
+ let y = 3;
+ return foo(x * y);
+}
+
+console.log(bar(7)); // 返回 42</pre>
+
+<p>当调用 <code>bar</code> 时,第一个帧被创建并压入栈中,帧中包含了 <code>bar</code> 的参数和局部变量。 当 <code>bar</code> 调用 <code>foo</code> 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含 <code>foo</code> 的参数和局部变量。当 <code>foo</code> 执行完毕然后返回时,第二个帧就被弹出栈(剩下 <code>bar</code> 函数的调用帧 )。当 <code>bar</code> 也执行完毕然后返回时,第一个帧也被弹出,栈就被清空了。</p>
+
+<h3 id="堆">堆</h3>
+
+<p>对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。</p>
+
+<h3 id="队列">队列</h3>
+
+<p>一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。</p>
+
+<p>在 <a href="#事件循环">事件循环</a> 期间的某个时刻,运行时会从最先进入队列的消息开始处理队列中的消息。被处理的消息会被移出队列,并作为输入参数来调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。</p>
+
+<p>函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。</p>
+
+<h2 id="事件循环">事件循环</h2>
+
+<p>之所以称之为 <strong>事件循环</strong>,是因为它经常按照类似如下的方式来被实现:</p>
+
+<pre class="brush: js notranslate">while (queue.waitForMessage()) {
+ queue.processNextMessage();
+}</pre>
+
+<p><code>queue.waitForMessage()</code> 会同步地等待消息到达(如果当前没有任何消息等待被处理)。</p>
+
+<h3 id="执行至完成">"执行至完成"</h3>
+
+<p>每一个消息完整地执行后,其它消息才会被执行。这为程序的分析提供了一些优秀的特性,包括:当一个函数执行时,它不会被抢占,只有在它运行完毕之后才会去运行任何其他的代码,才能修改这个函数操作的数据。这与C语言不同,例如,如果函数在线程中运行,它可能在任何位置被终止,然后在另一个线程中运行其他代码。</p>
+
+<p>这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时,Web应用程序就无法处理与用户的交互,例如点击或滚动。为了缓解这个问题,浏览器一般会弹出一个“这个脚本运行时间过长”的对话框。一个良好的习惯是缩短单个消息处理时间,并在可能的情况下将一个消息裁剪成多个消息。</p>
+
+<h3 id="添加消息">添加消息</h3>
+
+<p>在浏览器里,每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。所以当一个带有点击事件处理器的元素被点击时,就会像其他事件一样产生一个类似的消息。</p>
+
+<p>函数 <code><a href="/zh-CN/docs/Web/API/Window/setTimeout">setTimeout</a></code> 接受两个参数:待加入队列的消息和一个时间值(可选,默认为 0)。这个时间值代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息并且栈为空,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其它消息,<code>setTimeout</code> 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。</p>
+
+<p>下面的例子演示了这个概念(<code>setTimeout</code> 并不会在计时器到期之后直接执行):</p>
+
+<pre class="brush: js line-numbers language-js notranslate"><code class="language-js"><span class="keyword token">const</span> s <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Date</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">getSeconds</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+
+<span class="function token">setTimeout</span><span class="punctuation token">(</span><span class="keyword token">function</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="comment token">// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行</span>
+ console<span class="punctuation token">.</span><span class="function token">log</span><span class="punctuation token">(</span><span class="string token">"Ran after "</span> <span class="operator token">+</span> <span class="punctuation token">(</span><span class="keyword token">new</span> <span class="class-name token">Date</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">getSeconds</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="operator token">-</span> s<span class="punctuation token">)</span> <span class="operator token">+</span> <span class="string token">" seconds"</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+<span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="number token">500</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+
+<span class="keyword token">while</span><span class="punctuation token">(</span><span class="keyword token">true</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ <span class="keyword token">if</span><span class="punctuation token">(</span><span class="keyword token">new</span> <span class="class-name token">Date</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">getSeconds</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="operator token">-</span> s <span class="operator token">&gt;=</span> <span class="number token">2</span><span class="punctuation token">)</span> <span class="punctuation token">{</span>
+ console<span class="punctuation token">.</span><span class="function token">log</span><span class="punctuation token">(</span><span class="string token">"Good, looped for 2 seconds"</span><span class="punctuation token">)</span><span class="punctuation token">;</span>
+ <span class="keyword token">break</span><span class="punctuation token">;</span>
+ <span class="punctuation token">}</span>
+<span class="punctuation token">}</span></code></pre>
+
+<h3 id="零延迟">零延迟</h3>
+
+<p>零延迟并不意味着回调会立即执行。以 0 为第二参数调用 <code>setTimeout</code> 并不表示在 0 毫秒后就立即调用回调函数。</p>
+
+<p>其等待的时间取决于队列里待处理的消息数量。在下面的例子中,<code>"这是一条消息"</code> 将会在回调获得处理之前输出到控制台,这是因为延迟参数是运行时处理请求所需的最小等待时间,但并不保证是准确的等待时间。</p>
+
+<p>基本上,<code>setTimeout</code> 需要等待当前队列中所有的消息都处理完毕之后才能执行,即使已经超出了由第二参数所指定的时间。</p>
+
+<pre class="brush: js notranslate">(function() {
+
+ console.log('这是开始');
+
+ setTimeout(function cb() {
+ console.log('这是来自第一个回调的消息');
+ });
+
+ console.log('这是一条消息');
+
+ setTimeout(function cb1() {
+ console.log('这是来自第二个回调的消息');
+ }, 0);
+
+ console.log('这是结束');
+
+})();
+
+// "这是开始"
+// "这是一条消息"
+// "这是结束"
+// "这是来自第一个回调的消息"
+// "这是来自第二个回调的消息"
+</pre>
+
+<h3 id="多个运行时互相通信">多个运行时互相通信</h3>
+
+<p>一个 web worker 或者一个跨域的 <code>iframe</code> 都有自己的栈、堆和消息队列。两个不同的运行时只能通过 <a href="/zh-CN/docs/Web/API/Window/postMessage"><code>postMessage</code></a> 方法进行通信。如果另一个运行时侦听 <code>message</code> 事件,则此方法会向该运行时添加消息。</p>
+
+<h2 id="永不阻塞">永不阻塞</h2>
+
+<p>JavaScript的事件循环模型与许多其他语言不同的一个非常有趣的特性是,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待一个 <a href="/zh-CN/docs/Web/API/IndexedDB_API">IndexedDB</a> 查询返回或者一个 <a href="/zh-CN/docs/Web/API/XMLHttpRequest">XHR</a> 请求返回时,它仍然可以处理其它事情,比如用户输入。</p>
+
+<p>由于历史原因有一些例外,如 <code>alert</code> 或者同步 XHR,但应该尽量避免使用它们。注意,<a href="https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311">例外的例外也是存在的</a>(但通常是实现错误而非其它原因)。</p>
+
+<h2 id="标准规范">标准规范</h2>
+
+<table class="standard-table">
+ <thead>
+ <tr>
+ <th scope="col">标准规范</th>
+ <th scope="col">状态</th>
+ <th scope="col">注释</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>{{SpecName('HTML WHATWG', 'webappapis.html#event-loops', 'Event loops')}}</td>
+ <td>{{Spec2('HTML WHATWG')}}</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td><a href="https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop">Node.js 事件循环</a></td>
+ <td>Living Standard</td>
+ <td></td>
+ </tr>
+ </tbody>
+</table>