uTools-Manuals/docs/javascript/EventLoop.html
2019-04-21 11:50:48 +08:00

111 lines
12 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<article id="wikiArticle">
<div></div>
<p>JavaScript 的并发模型基于“事件循环”。这个模型与像 C 或者 Java 这种其它语言中的模型截然不同。</p>
<h2 id="运行时概念">运行时概念</h2>
<p>下面的内容解释了一个理论模型。现代 JavaScript 引擎实现并着重优化了所描述的这些语义。</p>
<h3 id="可视化描述">可视化描述</h3>
<p style="text-align: center;"><img alt="Stack, heap, queue" src="/files/4617/default.svg" style="height: 270px; width: 294px;"/></p>
<h3 id="栈"></h3>
<p>函数调用形成了一个栈帧。</p>
<pre><code class="language-javascript">function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42</code></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="#Event_loop">事件循环</a>期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。正如前面所提到的,调用一个函数总是会为其创造一个新的栈帧。</p>
<p>函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。</p>
<h2 id="事件循环">事件循环</h2>
<p>之所以称之为事件循环,是因为它经常按照类似如下的方式来被实现:</p>
<pre><code class="language-javascript">while (queue.waitForMessage()) {
queue.processNextMessage();
}</code></pre>
<p>如果当前没有任何消息,<code>queue.waitForMessage()</code> 会同步地等待消息到达。</p>
<h3 id="执行至完成">"执行至完成"</h3>
<p>每一个消息完整的执行后其它消息才会被执行。这为程序的分析提供了一些优秀的特性包括一个函数执行时它永远不会被抢占并且在其他代码运行之前完全运行且可以修改此函数操作的数据。这与C语言不同例如如果函数在线程中运行它可能在任何位置被终止然后在另一个线程中运行其他代码。</p>
<p>这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时Web应用就无法处理用户的交互例如点击或滚动。浏览器用“程序需要过长时间运行”的对话框来缓解这个问题。一个很好的做法是缩短消息处理并在可能的情况下将一个消息裁剪成多个消息。</p>
<h3 id="添加消息">添加消息</h3>
<p>在浏览器里,当一个事件发生且有一个事件监听器绑定在该事件上时,消息会被随时添加进队列。如果没有事件监听器,事件会丢失。所以点击一个附带点击事件处理函数的元素会添加一个消息,其它事件类似。</p>
<p>函数 <a class="new" href="/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>setTimeout</code></a> 接受两个参数:待加入队列的消息和一个延迟(可选,默认为 0。这个延迟代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其它消息在这段延迟时间过去之后消息会被马上处理。但是如果有其它消息<code>setTimeout</code> 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少延迟时间,而非确切的等待时间。</p>
<p>下面的例子演示了这个概念(<code>setTimeout</code> 并不会在计时器到期之后直接执行):</p>
<pre><code class="language-javascript"><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></code></pre>
<h3 id="零延迟">零延迟</h3>
<p>零延迟并不意味着回调会立即执行。以 0 为第二参数调用 <code>setTimeout</code> 并不表示在 0 毫秒后就立即调用回调函数。</p>
<p>其等待的时间取决于队列里待处理的消息数量。在下面的例子中,"this is just a message" 将会在回调获得处理之前输出到控制台,这是因为延迟参数是运行时处理请求所需的最小等待时间,但并不保证是准确的等待时间。</p>
<p>基本上,<code>setTimeout</code> 需要等待当前队列中所有的消息都处理完毕之后才能执行,即使已经超出了由第二参数所指定的时间。</p>
<pre><code class="language-javascript">(function() {
console.log('这是开始');
setTimeout(function cb() {
console.log('这是来自第一个回调的消息');
});
console.log('这是一条消息');
setTimeout(function cb1() {
console.log('这是来自第二个回调的消息');
}, 0);
console.log('这是结束');
})();
// "这是开始"
// "这是一条消息"
// "这是结束"
// 此处,函数返回了 undefined
// "这是来自第一个回调的消息"
// "这是来自第二个回调的消息"
</code></pre>
<h3 id="多个运行时互相通信">多个运行时互相通信</h3>
<p>一个 web worker 或者一个跨域的 <code>iframe</code> 都有自己的栈,堆和消息队列。两个不同的运行时只能通过 <a href="/zh-CN/docs/Web/API/Window/postMessage" title="window.postMessage() 方法可以安全地实现跨源通信。通常对于两个不同页面的脚本只有当执行它们的页面位于具有相同的协议通常为https端口号443为https的默认值以及主机  (两个页面的模数 Document.domain设置为相同的值) 时这两个脚本才能相互通信。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" title="IndexedDB 是一种低级API用于客户端存储大量结构化数据(包括, 文件/ blobs)。该API使用索引来实现对该数据的高性能搜索。虽然 Web Storage 对于存储较少量的数据很有用但对于存储更大量的结构化数据来说这种方法不太有用。IndexedDB提供了一个解决方案。"><code>IndexedDB</code></a> 查询返回或者一个 <a href="/zh-CN/docs/Web/API/XMLHttpRequest" title="使用XMLHttpRequest (XHR)对象可以与服务器交互。您可以从URL获取数据而无需让整个的页面刷新。这使得Web页面可以只更新页面的局部而不影响用户的操作。XMLHttpRequest在 Ajax 编程中被大量使用。"><code>XHR</code></a> 请求返回时,它仍然可以处理其它事情,比如用户输入。</p>
<p>遗留的例外是存在的,如 <code>alert</code> 或者同步 XHR但应该尽量避免使用它们。注意<a class="external" href="https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311" rel="noopener">例外的例外也是存在的</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><a class="external" href="https://html.spec.whatwg.org/multipage/webappapis.html#event-loops" hreflang="en" lang="en" rel="noopener">HTML Living Standard<br/><small lang="zh-CN">Event loops</small></a></td>
<td><span class="spec-Living">Living Standard</span></td>
<td> </td>
</tr>
<tr>
<td><a class="external" href="https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop" rel="noopener">Node.js 事件循环</a></td>
<td>Living Standard</td>
<td> </td>
</tr>
</tbody>
</table>
<p> </p>
</article>