Javascript并发模型和事件循环
JavaScript的并发模型基于“事件循环”
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
- 当调用bar时,创建了第一个帧,帧中包含了bar的参数和局部变量
- 当bar中调用foo时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了foo的参数和局部变量
- 当foo返回时,最上层的帧就被弹出栈(剩下bar的调用栈),当bar返回的时候,栈就空了
堆
对象被分配在一个堆中,即用以表示一大块非结构化的内存区域
队列
一个JavsScript运行时包含了一个待处理的消息队列;每一个消息都关联着一个用以处理这个消息的函数。
在 事件循环 期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个队列消息会被移出队列,并作为输入参数调用与之关联的函数。正如前面所提到,调用一个函数总是会为其创造一个新的栈帧。
函数的处理会一直进行到执行栈再次为空未知;然后事件循环将会处理队列中下一个消息(如果还有的话)
事件循环
之所以称之为事件循环,是因为它经常按照类似如下的方式来被实现:
while(queue.waitForMessage()) {
queue.processNextMessage();
}
如果当前没有任何消息,queue.waitForMessage()会同步等待信息到达
"执行至完成"
每一个消息完整地执行后,其他消息才会被执行
这为程序分析提供了一些优秀的特性,包括,一个函数执行时,它永远不会被抢占,并且在其他代码运行之前完全运行(且可以修改此函数操作的数据)
这个模型的缺点在于当一个消息需要太长时间才能处理完毕时,web应用就无法处理用户的交互,例如点击或滚动。 一个很好的做法就是缩短消息处理,并在可能的情况下将一个消息裁剪成多个消息。
添加消息
在浏览器里,当一个事件发生且有一个事件监听器绑定在该事件上时,消息会被随时添加进队列。如果没有事件监听器,事件就会丢失。所以点击一个附带点击事件处理函数的元素会添加一个消息,其他事件类似。
<div id="box"></div>
var box = document.getElementById('box');
box.onClick = function() { console.log('click') } // 绑定了事件监听器
box.onClick = null // 没有事件监听器,事件就会丢失
setTimeout
函数setTimeout接受两个参数: 待加入队列的消息和一个延迟(可选,默认为0)。 这个延迟代表了消息被实际加入到队列的最小延迟时间。如果队列中没有其他消息,在这段延迟时间过去之后,消息会被马上处理。但是,如果有其他消息,setTimeout消息必须等待其他消息处理完。
setTimeout(function() {
console.log("zhixing")
}, 200}
因此,第二个参数(200)仅仅表示最少延迟时间,而非确切的等待时间。
const s = new Date().getSeconds();
setTimeout(function() {
// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);
while(true) {
if(new Date().getSeconds() - s >= 2) {
console.log("Good, looped for 2 seconds");
break;
}
}
零延迟
零延迟并不意味着回调会立即执行。以0为第二个参数用setTimeout并不表示0毫秒后立即调用回调函数。
其等待的时间取决于队列里待处理的消息数量。
基本上setTimeout需要等待当前队列中所有的消息都处理完毕之后才能执行,即使已经超出了由第二参数所指定的时间。
(function() {
console.log('这是开始');
setTimeout(function cb() {
console.log('这是来自第一个回调的消息');
});
console.log('这是一条消息');
setTimeout(function cb1() {
console.log('这是来自第二个回调的消息');
}, 0);
console.log('这是结束');
})();
// "这是开始"
// "这是一条消息"
// "这是结束"
// "这是来自第一个回调的消息"
// "这是来自第二个回调的消息"
多个运行时互相通信
一个web worker或则一个跨域的iframe都有自己的栈,堆和消息队列。两个不同的运行时只能通过postMessage方法进行通信。如果另一个运行时侦听message事件,则此方法会向该运行时添加消息。
永不阻塞
事件循环模型的一个非常有趣的特性是,与许多其他语言不同,JavaScript永不阻塞。处理I/O通常通过事件和回调来执行,所以当一个应用正等待一个IndexedDB查询返回或则一个XHR请求返回时,它仍然可以处理其它事情,比如用户输入。
遗留的意外也是存在的,如alert或者通过XHR,但应该尽量避免使用它们
我只是MDN搬运工 (MDN地址)developer.mozilla.org/zh-CN/docs/…
转载自:https://juejin.cn/post/6844903924483227662