这是我参与更文挑战的第20天,活动详情查看: 更文挑战javascript
面试官:给我说说事件循环吧,就是EventLoop的机制,这个你据说过吧。html
我:...💥💥💥🚀java
先看这张图,先无论宏任务,微任务是什么,先看整个流程。web
分析:面试
判断宏任务队列是否为空promise
判断微任务队列是否为空浏览器
由于首次执行宏队列中会有 script(总体代码块)任务,因此实际上就是 Js 解析完成后,在异步任务中,会先执行完全部的微任务,这里也是不少
面试题喜欢考察的。
须要注意的是,新建立的微任务会当即进入微任务队列排队执行,不须要等待下一次轮回。markdown
主线程从任务队列读取事件,这个过程是循环不断的,因此整个运行机制又称为 Event Loop(事件循环)。
数据结构
在深刻事件循环机制以前,须要弄懂一下几个概念:异步
执行上下文( Execution context )
执行栈( Execution stack )
微任务( micro-task )
宏任务( macro-task )
执行上下文( Execution context )
执行上下文是一个抽象的概念,能够理解为是代码执行的一个环境。JS 的执行上下文分为三种,
全局执 行上下文、函数(局部)执行上下文、Eval 执行上下文。
全局执行上下文
:全局执行上下文指的是全局 this 指向的 window ,能够是外部加载的 JS 文件 或者本地 标签中的代码。函数执行上下文
:函数上下文也称为局部上下文,每一个函数被调用的时候,都会建立一个新的局部 上下文。执行栈( Execution stack )
执行栈,就是咱们数据结构中的“栈”,它具备“先进后出”的特色,正是由于这种特色,在咱们代码进行 执行的时候,遇到一个执行上下文就将其依次压入执行栈中。
当代码执行的时候,先执行位于栈顶的执行上下文中的代码,当栈顶的执行上下文代码执行完毕就会出 栈,继续执行下一个位于栈顶的执行上下文。
function foo() {
console.log('a');
bar();
console.log('b');
}
function bar() {
console.log('c');
}
foo();
复制代码
咱们都知道 Js 是单线程都,可是一些高耗时操做就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:
同步模式(Synchronous)和异步模式(Asynchronous)
。
在异步模式下,建立异步任务主要分为宏任务与微任务两种。ES6 规范中,
宏任务(Macrotask) | 微任务(Microtask) |
---|---|
setTimeout | requestAnimationFrame(有争议) |
setInterval | MutationObserver(浏览器环境) |
MessageChannel | Promise.[ then/catch/finally ] |
I/O,事件队列 | process.nextTick(Node环境) |
setImmediate(Node环境) | queueMicrotask |
script(总体代码块) |
注意:nextTick 队列会比 Promie 队列先执行。
如何理解 script(总体代码块)是个宏任务呢 🤔
实际上若是同时存在两个 script 代码块,会首先在执行第一个 script 代码块中的同步代码,若是这个过程当中建立了微任务并进入了微任务队列,第一个 script 同步代码执行完以后,会首先去清空微任务队列,再去开启第二个 script 代码块的执行。
因此这里应该就能够理解 script(总体代码块)为何会是宏任务。
以上概念弄明白以后,再来看循环机制是如何运行的呢?如下涉及到的任务执行顺序都是靠函数调用栈 来实现的。
script
标签内的代码开始的,上边咱们提到过,整个script
标签 做为一个宏任务处理的。setTimeout
,就会将当前任务分发到对应的执行队 列中去。 Promise
,在建立 Promise
实例对象时,代码顺序执行,若是 到了执行· then
操做,该任务就会被分发到微任务队列中去。script
标签内的代码执行完毕,同时执行过程当中所涉及到的宏任务也和微任务也分配到相应的队 列中去。setTimeout
任务队列除此以外,还能够放定时器的回调函数,须要指定某些代码多少时间以后执行。
定时器主要包括两种, setTimeout 和 setInterval 两个函数
。 当咱们设置定时器的时间,执行某个特定的任务,以下:
// 1 秒后执行
setTimeout(function () {
console.log(2);
}, 1000);
console.log(1)
复制代码
上述的输出结果为 1, 2,执行完同步代码后,就会执行定时器中的任务事件
// 同步执行完当即执行
setTimeout(function () {
console.log(2);
}, 0);
console.log(1)
复制代码
当咱们执行 setTimeout(fn,0)
定时器时,会将这个定时任务回调放在任务队列的尾部,表明的含 义就是尽早的执行。
也就是等到主线程同步任务和"任务队列"现有的事件都处理完,而后才会当即执行这个定时器的任务。
上述的前提是,等到同步任务和任务队列的代码执行完毕后,若是当前代码执行很长时间,定时器并没 办法保证必定在指定时间执行。
注:HTML5 标准规定了setTimeout() 的第二个参数的最小值(最短间隔),不得低于4毫秒, 若是低于这个值,就会自动增长。
若是涉及到页面的改动,这个定时器任务一般不会当即执行,而是 16 毫秒执行一次,咱们一般使用 requestAnimationFrame() 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>消息运行机制</title>
</head>
<body>
</body>
<script> console.log('1'); setTimeout(() => { console.log('2') }, 1000); new Promise((resolve, reject) => { console.log('3'); resolve(); console.log('4'); }).then(() => { console.log('5'); }); console.log('6');// 1,3,4,6,5,2 </script>
</html>
复制代码
分析:
<script>
标签内的同步代码,此时全局的代码进入执行栈中,同步顺序执行代码,输出 1。setTimeout (宏任务)
,将其分配到宏任务异步队列中。 promise 异步代码(微任务)
。可是构造函数中的代码为同步代码,依次输出三、4,则then
以后的任务加入到微任务队列中去。script
内的代码做为宏任务处理,因此这次循环进行处处理微任务队列中的全部异步任务,直达微任务队列中的全部任务执行完成为止,微任务队列中只有一个微任务,因此输出 5。 setTimeout
,最后输出 2。以上不免有些啰嗦,因此简化整理以下步骤: