事件队列 事件循环 EventLoop 宏任务 微任务详解
- 一、概念
- 二、宏任务(多个)、微任务(1个)
- 三、Promise 的构造函数
- 四、process.nextTick在事件循环中的处理
- 五、vue nextTick原理
一、概念
event: 事件
loop: 循环,循环的是一个又一个的任务队列
任务队列: 是一个先进先出的数据结构, 排在前面的事件, 优先被主进程读取
任务队列分为: 宏队列, 微队列, 分别存放在宏任务和微任务
ES6 规范中,microtask 称为 jobs,macrotask 称为 task,宏任务是由宿主发起的,而微任务由JavaScript自身发起。
二、宏任务(多个)、微任务(1个)
我们常见的点击和键盘等事件也宏任务
- 常见的宏任务
- setTimeout()
- setInterval()
- setImmediate()
- script
- 常见的微任务
- promise.then()
- promise.catch()
- new MutaionObserver()
- process.nextTick()
-
本质区别
宏任务特征: 有明确的异步任务需要执行和回调; 需要其他异步线程支持。
微任务特征: 没有明确的异步任务需要执行,只有回调;不需要其他异步线程支持。 -
执行顺序(这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop 事件循环)
console.log = promise -> promise.nextTick -> promise.then -> setTimeout -> setImmediate
①先按代码顺序将同步任务压入主执行栈中执行
②遇到异步任务则先将异步任务压入对应的任务队列中(宏队列或微队列)
③同步任务执行完毕后,查看微队列,将微任务一一取出进入主执行栈中执行
④微任务队列清空后,再查看宏队列,只取出第一个宏任务执行,执行完
- 首先我们分析有多少个宏任务;
- 在每个宏任务中,分析有多少个微任务;
- 根据调用次序,确定宏任务中的微任务执行次序;
- 确定整个顺序。
then中的回调函数要确定Promise状态后才能压入微队列
Event Loop 在压入事件时,都会判断微任务队列是否还有需要执行的事件:如果有,则优先将需要执行的微任务压入;没有,则依次压入需要执行宏任务!
// Promise 中的执行顺序
const promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(() => {
console.log('resolved');
});
console.log('Hi!'); // Promise Hi!resolved
// 执行顺序
setTimeout(function () {
console.log(1);
});
new Promise(function (resolve,reject) {
console.log(2);
resolve(3);
}).then(function (val) {
console.log(val);
new Promise((resolve,reject) => {
console.log(5);
resolve(7);
}).then(function (val) {
console.log(val);
});
});
console.log(4);
// 先按顺序执行同步任务
// Promise 新建后立即执行输出2, 接着输出4, 异步任务等同步任务执行完成后执行
// 且同一次事件循环中, 微任务永远在宏任务之前执行。这时候执行栈空了,执行事件队列,
// 先取出微任务, 输出3, 最后取出宏任务, 输出1
三、Promise 的构造函数
Promise 构造函数是 JavaScript 中用于创建 Promise 对象的内置构造函数。
Promise 构造函数接受一个函数作为参数,该函数是同步的并且会被立即执行,所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject,分别表示 Promise 成功和失败的状态。
起始函数执行成功时,它应该调用 resolve 函数并传递成功的结果。当起始函数执行失败时,它应该调用 reject 函数并传递失败的原因。
Promise 构造函数返回一个 Promise 对象,该对象具有以下几个方法:
then:用于处理 Promise 成功状态的回调函数。
catch:用于处理 Promise 失败状态的回调函数。
finally:无论 Promise 是成功还是失败,都会执行的回调函数。
当 Promise 被构造时,起始函数会被同步执行;
四、process.nextTick在事件循环中的处理
process.nextTick是Node环境的变量,process.nextTick() 是一个特殊的异步API,其不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。所以,nextTick和Promise同时出现时,肯定是process.nextTick() 先执行。
可以类比 Vue.$nextTick(),也是需要执行完这个函数后,才能继续Event Loop。
五、vue nextTick原理
Vue.nextTick() 是一个方法,用于在下次 DOM 更新循环结束之后执行延迟回调。它的实现原理是利用浏览器的异步任务队列机制,在 tick 时刻将回调函数放入队列中等待执行。在实现上,nextTick 方法会根据当前环境选择不同的底层实现。在现代浏览器中,它使用了 MutationObserver 和 Promise 等技术实现异步任务调度;在旧版浏览器中,则使用了 setTimeout 来模拟异步任务。
Vue.nextTick()的实现原理主要是将回调函数推入到一个队列中,在下一个事件循环周期(MacroTask)中执行这个队列中的所有回调函数。具体来说,当用户使用 Vue.nextTick()执行回调函数时,Vue.js 会按照以下步骤进行处理:
1.首先,Vue.js 会将回调函数推入到一个队列中。这个队列称为“异步更新队列”(Async Queue),它是 Vue.js 用于收集在同一事件循环周期内需要执行的所有异步任务的容器。
2.接着,Vue.js 会判断当前是否存在一个微任务(MicroTask)队列。如果存在,则将异步更新队列合并到微任务队列中;否则,创建一个新的微任务队列,并将异步更新队列添加到其中。
3.接着,Vue.js 会将当前执行上下文捕获并保存下来。这个上下文包含了当前执行 Vue.nextTick()方法的组件实例、数据变化等信息。
4.最后,Vue.js 会将一个微任务添加到微任务队列中。这个微任务的作用是在下一个事件循环周期中执行异步更新队列中的所有回调函数,并且在执行之前恢复上下文,确保回调函数能够正确地访问到相关数据。
// 下面是一个简单的示例代码,演示了 Vue.nextTick()的使用方法和实现原理:
// 定义一个 Vue 实例
var vm = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
},
});
// 在数据更新后执行回调函数
vm.message = 'Hello, Vue.js!';
Vue.nextTick(function () {
console.log('DOM updated!');
});
// 输出:'DOM updated!'
在这个示例中,我们首先定义了一个 Vue 实例,并通过数据绑定将 message 属性绑定到了页面上。然后,我们通过修改 message 属性的值来触发视图更新,并在 Vue.nextTick()方法中添加了一个回调函数来检查 DOM 是否已经更新。
当我们运行这段代码时,Vue.js 会按照上述步骤进行处理,并在下一个事件循环周期中执行回调函数。因此,我们可以在控制台中看到输出结果。
总的来说,Vue.nextTick()提供了一种非常方便的方式来处理 DOM 更新后的回调函数,可以帮助我们避免一些常见的问题,例如在获取 DOM 元素的位置或尺寸时可能会遇到的延迟问题。同时,它的实现也为我们提供了一种思路,
与 node 的 nextTick 的区别
Vue.nextTick和 Node.js 的process.nextTick虽然名字相似,但是它们的功能和用途不同。
Vue.nextTick是 Vue.js 提供的一个方法,主要用于在 DOM 更新之后执行某些操作,例如更新完数据后获取更新后的 DOM 节点。它利用了浏览器的异步渲染机制,将回调函数推迟到下一个 DOM 更新周期中执行。
process.nextTick是 Node.js 提供的一个方法,主要用于在当前事件循环的末尾、下一次事件循环之前执行一些操作。它可以让你在当前事件循环中的所有 I/O 操作完成后立即执行回调函数,而不必等待下一次事件循环。
因此,Vue.nextTick和process.nextTick虽然名称相似,但是它们的作用和使用场景不同,不能互相替代。