mirror of
https://github.com/fofolee/uTools-Manuals.git
synced 2025-06-08 23:14:06 +08:00
111 lines
12 KiB
HTML
111 lines
12 KiB
HTML
<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">>=</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> |