如何读懂JavaScript的事件循环机制,要点有哪些
Admin 2022-06-17 群英技术资讯 329 次浏览
这次主要整理一下自己对 Js事件循环机制,同步,异步任务,宏任务,微任务的理解,大概率暂时还有些偏差或者错误。如果有,十分欢迎各位纠正我的错误!
首先,JS是单线程,这样设计也是具有合理性的,试想如果一边进行dom的删除,另一边又进行dom的添加,浏览器该如何处理?
引用:
“单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,串行执行任务;异步执行就是cpu跳过等待,先处理后续的任务(CPU与网络模块、timer等并行进行任务)。由此产生了任务队列与事件循环,来协调主线程与异步模块之间的工作。“”
图解:
首先把JS执行代码操作 分为主线程
,任务队列
,任何一段js代码的执行都可以分为以下几个步骤:
步骤一: 主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
步骤二: 当主线程遇到异步操作的时候,将异步操作交给对应的API进行处理;
步骤三: 当异步操作处理完成,推入任务队列中
步骤四: 主线程执行完毕后,查询任务队列,取出一个任务,并推入主线程进行处理
步骤五: 重复步骤二、三、四
其中常见的异步操作有:ajax请求,setTimeout,还有类似onclik事件等
等
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列
首先,顾名思义,既然是一个队列,那么就遵循FIFO
原则
如上示意图,任务队列存在多个,它们的执行顺序:
同一任务队列内,按队列顺序被主线程取走;
不同任务队列之间,存在着优先级,优先级高的优先获取(如用户I/O)
任务队列分为 宏任务(macrotask queue)
和 微任务(microtask queue)
宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
微任务microtask queue:
(1) 唯一,整个事件循环当中,仅存在一个;
(2) 执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;
PS:所以利用microtask queue可以形成一个同步执行的环境
宏任务macrotask queue
:
(1) 不唯一,存在一定的优先级(用户I/O部分优先级更高)
(2) 异步执行,同一事件循环中,只执行一个
先用一个简单的例子加深一下理解:
console.log('1, time = ' + new Date().toString()) // 1.进入主线程,执行同步任务,输出1 setTimeout(macroCallback, 0)// 2. 加入宏任务队列 // 7.开始执行此定时器宏任务,调用macroCallback,输出4 new Promise(function (resolve, reject) {//3.加入微任务队列 console.log('2, time = ' + new Date().toString())//4.执行此微任务中的同步代码,输出2 resolve() console.log('3, time = ' + new Date().toString())//5.输出3 }).then(microCallback)// 6.执行then微任务,调用microCallback,输出5 //函数定义 function macroCallback() { console.log('4, time = ' + new Date().toString()) } function microCallback() { console.log('5, time = ' + new Date().toString()) }
运行结果:
第一次看见这东西,有点眼熟啊,想了一下好像之前vue项目中 用过 this.$nextTick(callback)
当时说的是 当页面上元素被重新渲染之后 才会执行回调函数中的代码
,不是很理解,暂时记住吧
任何时候在给定的阶段中调用 process.nextTick(),所有传递到 process.nextTick() 的回调将在事件循环继续之前解析
在事件循环中,每进行一次循环操作称为tick
,知道了这个之后,对理解这个方法什么时候调用瞬间明白了一些!
再借用别人的例子,加深一下对事件循环的理解吧:
var flag = false // 1. 变量声明 Promise.resolve().then(() => { // 2. 将 then 任务分发到本轮循环微任务队列中去 console.log('then1') // 8. 执行 then 微任务, 打印 then1,flag 此时是 true 了 flag = true }) new Promise(resolve => { // 3. 执行 Promise 里 同步代码 console.log('promise') resolve() setTimeout(() => { // 4. 将定时器里的任务放到宏任务队列中 console.log('timeout2') // 11. 执行定时器宏任务 这边指定了 10 的等待时长, 因此在另一个定时器任务之后执行了 }, 10) }).then(function () { // 5. 将 then 任务分发到本轮循环微任务队列中去 console.log('then2') // 9. 执行 then 微任务, 打印 then2,至此本轮 tick 结束 }) function f1(f) { // 1. 函数声明 f() } function f2(f) { // 1. 函数声明 setTimeout(f) // 7. 把`setTimeout`中的`f`放到宏任务队列中,等本轮`tick`执行完,下一次事件循环再执行 } f1(() => console.log('f为:', flag ? '异步' : '同步')) // 6. 打印 `f为:同步` f2(() => { console.log('timeout1,', 'f为:', flag ? '异步' : '同步') // 10. 执行定时器宏任务 }) console.log('本轮宏任务执行完') // 7. 打印
运行结果:
process.nextTick 中的回调是在当前tick执行完之后,下一个宏任务执行之前调用的。
官方的例子:
let bar; // 这个方法用的是一个异步签名,但其实它是同步方式调用回调的 function someAsyncApiCall(callback) { callback(); } // 回调函数在`someAsyncApiCall`完成之前被调用 someAsyncApiCall(() => { // 由于`someAsyncApiCall`已经完成,bar没有被分配任何值 console.log('bar', bar); // undefined }); bar = 1;
使用 process.nextTick
:
let bar; function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1;
再看一个含有 process.nextTick
的例子:
console.log('1'); // 1.压入主线程执行栈,输出1 setTimeout(function () { //2.它的回调函数被加入 宏任务队列中 //7.目前微任务队列为空,所以取出 宏任务队列首项,执行此任务 console.log('2'); // 输出2 process.nextTick(function () { // 16.上一次循环结束,在下一次宏任务开始之前调用,输出3 console.log('3'); }) new Promise(function (resolve) { //8.执行 此promise的同步任务,输出4,状态变为resolve console.log('4'); resolve(); }).then(function () {//9.检测到异步方法then,将其回调函数加入 微任务队列中 console.log('5'); // 10. 取出微任务队列首项,也就是这个then的回调,执行,输出5 }) }) process.nextTick(function () { // 11.一次事件循环结束,执行nextTick()的回调,输出6 console.log('6'); }) new Promise(function (resolve) { //3.执行promise中的同步任务 输出7,状态变为resolve console.log('7'); resolve(); }).then(function () { //4.检测到异步方法then,将其回调函数加入 微任务队列中 console.log('8'); //6. 主线程执行完毕,取出微任务队列中首项,将其回调函数压入执行栈,输出8 }) setTimeout(function () { //5.它的回调函数 加入 宏任务队列中 //12.此刻,微任务队列为空,开始执行此宏任务 console.log('9'); // 输出9 process.nextTick(function () { // 17.此刻 微任务和宏任务队列都为空了,此次循环自动结束,执行此回调,输出10 console.log('10'); }) new Promise(function (resolve) { //13. 执行此promise的同步任务,输出11,状态改变 console.log('11'); resolve(); }).then(function () {//14.检测到then异步方法,加入微任务队列 console.log('12');//15.取出微任务队列首项,执行此then微任务,输出12 }) })
运行结果:
此过程步骤详解:
宏任务(macrotask)
队列中;微任务(microtask)
队列中;宏任务(macrotask)
队列中;结束!趁着灵光乍现的时候,噼里啪啦赶紧记录下来,后面再检查检查是否有问题,也欢迎各位指出我的错误。
再来分析一个简单的例子:
console.log('0'); setTimeout(() => { console.log('1'); new Promise(function(resolve) { console.log('2'); resolve(); }).then(()=>{ console.log('3'); }) new Promise(resolve => { console.log('4'); for(let i=0;i<9;i++){ i == 7 && resolve(); } console.log('5'); }).then(() => { console.log('6'); }) })
代码运行结果:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
在jquery中可以给一个元素设置点击事件吗在jquery中可以给一个元素设置点击事件设置方法如下:1、获取指定的元素对象语法为:$("selector")2、给获取到的元素对象添加点击事件
async函数的返回值为 promise 对象,promise对象的结果由async函数执行的返回值决定。async函数能使得异步操作变得更加方便,简而言之就是 Generator 的语法糖。
目录vue当前页push当前页无效vue push报错TypeError: Cannot read property ‘push‘ of undefined解决方法vue当前页push当前页无效当在当前页面中push页面跳转当前页,只是push的参数不同时,只能用字符串拼接,parames和query都不会起作用。不知
怎么使用node实现一个图片拼接插件?下面本篇文章给大家介绍一下使用node封装一个图片拼接插件的方法,希望对大家有所帮助!
node.js的全局变量怎么样使用?新手肯定对于node.js的全局变量不是很了解,对此,下面小编就能给介绍一下全局对象,全局函数,全局变量和准全局变量等等,了解这些对大家学习node.js的全局变量有一定的帮助,下面我们一起来了解看看吧。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008