20分钟10张动图8个示例带你更好的理解EventLoop

介绍

   JavaScript 主要在 Node.js 和浏览器中的单个线程上执行(有一些例外,例如工作线程,这超       出了当前文章的范围)。在本文中,我将尝试解释 Node.js 的并发机制,即事件循环。

 

例子

我相信通过示例学习是最好的,因此我将从 4 个简单的代码示例开始。我将分析示例,然后深入研究 Node.js 的架构。

Example 1:

 

console.log(1);
console.log(2);
console.log(3);
// Output:
// 1
// 2
// 3

 这个例子很简单,

第一步 console.log(1) 进入调用栈,被执行然后删除,

第二步 console.log(2) 进入调用栈,被执行然后删除,以此类推 console.log(3)。

示例 1 过程的可视化 

 Example 2:

console.log(1);
setTimeout(function foo(){
    console.log(2);
}, 0);
console.log(3);
// Output:
// 1
// 3
// 2 

我们可以在这个例子中看到我们立即运行 setTimeout,所以我们希望 console.log(2) 在 console.log(3) 之前,但事实并非如此,让我们了解它背后的机制。

基本事件循环架构(我们稍后会深入探讨)

 

  • 网络API:它们内置于您的网络浏览器中,能够从浏览器和周围的计算机环境中公开数据,并用它来做有用的复杂事情。它们不是 JavaScript 语言本身的一部分,而是构建在核心 JavaScript 语言之上,为您提供在 JavaScript 代码中使用的额外超能力。例如,Geolocation API 提供了一些简单的 JavaScript 结构来检索位置数据,因此您可以说,在 Google 地图上绘制您的位置。在后台,浏览器实际上使用一些复杂的低级代码(例如 C++)与设备的 GPS 硬件(或任何可用于确定位置数据的东西)通信,检索位置数据,并将其返回给浏览器环境以供使用在你的代码中。但同样,这种复杂性已通过 API 从您那里抽象出来。
  • Event Loop & Callback Queue:完成Web Apis执行的函数被移动到Callback Queue,这是一个常规的队列数据结构,Event Loop负责从Callback Queue中取出下一个函数并将函数发送到执行函数的调用堆栈

执行顺序

  1. 当前在调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
  2. 当调用栈为空时,所有排队的任务被一个一个地弹出到调用栈并执行,然后它们被弹出调用栈

让我们理解示例2

  1. console.log(1) 方法被调用并放置在调用堆栈上并被执行。

2. setTimeout 方法被调用并放置在调用堆栈上并被执行,此执行创建一个对 setTimeout Web Api 的新调用 0 毫秒,当它完成时(立即,或者更准确地说,它会更好“尽快”),Web Api 将调用移至回调队列。

3. console.log(3) 方法被调用并放置在调用栈中并被执行。

4. Event Loop 发现 Call Stack 为空,从 Callback Queue 中取出“foo”方法并将其放入 Call Stack,然后执行 console.log(2)。

示例 2 过程的可视化 

 setTimeout(function, delay)中的delay参数并不代表函数执行后的精确延时时间。它代表等待函数执行的最短时间。

Example 3:

console.log(1);
setTimeout(function foo() {
    console.log(‘foo’);
}, 3500);
setTimeout(function boo() {
    console.log(‘boo’);
}, 1000);
console.log(2);
// Output:
// 1
// 2
// 'boo'
// 'foo'

 

示例 3过程的可视化 

 Example 4:

console.log(1);
setTimeout(function foo() {
    console.log(‘foo’);
}, 6500);
setTimeout(function boo() {
    console.log(‘boo’);
}, 2500);
setTimeout(function baz() {
    console.log(‘baz’);
}, 0);
for (const value of [‘A’, ‘B’]) {
    console.log(value);
}
function two() {
    console.log(2);
}
two();
// Output:
// 1
// 'A'
// 'B'
// 2
// 'baz'
// 'boo'
// 'foo'

 

示例 4 过程的可视化 

 事件循环继续执行任务队列中等待的所有回调。在任务队列中,任务大致分为两类,即微任务Micro-Task 和宏任务macro-tasks。

宏任务(任务队列)和微任务

更准确地说,实际上有两种类型的队列。
1.宏任务队列(或简称任务队列)。
2.微任务队列。

  • 常见的宏任务是 setTimeout、setInterval 和 setImmediate。
  • 常见的 Micro-Task 有 process.nextTick 和 Promise 回调。

执行顺序

  1. 当前在调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
  2. 当调用栈为空时,所有排队的微任务被一个一个弹出到调用栈并执行,然后从调用栈中弹出。
  3. 当调用栈和微任务队列都为空时,所有排队的宏任务被一个一个弹出到调用栈并执行,然后被弹出调用栈。

Example 5:

console.log(1);
setTimeout(function foo() {
    console.log(‘foo’);
}, 0);
Promise.resolve()
    .then(function boo() {
        console.log(‘boo’);
    });
console.log(2);
// Output:
// 1
// 2
// 'boo'
// 'foo'
  1. console.log(1) 方法被调用并放置在调用堆栈上并被执行。
  2. 正在执行 SetTimeout,将 console.log('foo') 移至 SetTimeout Web Api,0 毫秒后移至Macro -Task Queue。
  3. Promise.resolve() 被调用,它被解析,然后 .then() 方法被移动到Micro -Task 队列。
  4. console.log(2) 方法被调用并放置在调用堆栈上并被执行。
  5. Event Loop 看到调用栈是空的,它首先从 Micro-Task 队列中取出任务,即 Promise 任务,将 console.log('boo') 放在调用栈上并执行它。
  6. 事件循环看到调用栈为空,然后它看到微任务为空,然后它从宏任务队列中取出下一个任务,即 SetTimeout 任务,将 console.log('foo')在调用堆栈上并执行它。

 示例 5 过程的可视化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脚本大神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值