前言:

这次主要整理一下自己对 Js事件循环机制,同步,异步任务,宏任务,微任务的理解,大概率暂时还有些偏差或者错误。如果有,十分欢迎各位纠正我的错误!

一、事件循环和任务队列产生的原因:

首先,JS是单线程,这样设计也是具有合理性的,试想如果一边进行dom的删除,另一边又进行dom的添加,浏览器该如何处理?

引用:

单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,串行执行任务;异步执行就是cpu跳过等待,先处理后续的任务(CPU与网络模块、timer等并行进行任务)。由此产生了任务队列与事件循环,来协调主线程与异步模块之间的工作。“”

二、事件循环机制:

图解:

首先把JS执行代码操作 分为主线程任务队列,任何一段js代码的执行都可以分为以下几个步骤:

步骤一: 主线程读取JS代码,此时为同步环境,形成相应的堆和执行栈;
步骤二: 当主线程遇到异步操作的时候,将异步操作交给对应的API进行处理;
步骤三: 当异步操作处理完成,推入任务队列中
步骤四: 主线程执行完毕后,查询任务队列,取出一个任务,并推入主线程进行处理
步骤五: 重复步骤二、三、四

其中常见的异步操作有:ajax请求,setTimeout,还有类似onclik事件等

三、任务队列:

同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列

首先,顾名思义,既然是一个队列,那么就遵循FIFO原则

如上示意图,任务队列存在多个,它们的执行顺序:

同一任务队列内,按队列顺序被主线程取走;
不同任务队列之间,存在着优先级,优先级高的优先获取(如用户I/O)

3.1 任务队列 的类型:

任务队列分为 宏任务(macrotask queue)微任务(microtask queue)

宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)

微任务主要包含:Promise、MutationObserver、process.nextTick(Node.js 环境)

3.2 两者区别:

微任务microtask queue:

(1) 唯一,整个事件循环当中,仅存在一个;
(2) 执行为同步,同一个事件循环中的microtask会按队列顺序,串行执行完毕;

PS:所以利用microtask queue可以形成一个同步执行的环境

宏任务macrotask queue:

(1) 不唯一,存在一定的优先级(用户I/O部分优先级更高)
(2) 异步执行,同一事件循环中,只执行一个

3.3 更细致的事件循环过程
  • 一、二、三、步同上
  • 主线程查询任务队列,执行microtask queue,将其按序执行,全部执行完毕;
  • 主线程查询任务队列,执行macrotask queue,取队首任务执行,执行完毕;
  • 重复四、五步骤;

先用一个简单的例子加深一下理解:

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.执行此微任务中的同步代码,输出2resolve()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())
}

运行结果:

四、强大的异步专家 process.nextTick()

第一次看见这东西,有点眼熟啊,想了一下好像之前vue项目中 用过 this.$nextTick(callback) 当时说的是 当页面上元素被重新渲染之后 才会执行回调函数中的代码
,不是很理解,暂时记住吧

process是node中的一个全局对象,可以通过它获得或修改进程相关信息

4.1 process.nextTick()在何时调用?

任何时候在给定的阶段中调用 process.nextTick(),所有传递到 process.nextTick() 的回调将在事件循环继续之前解析

在事件循环中,每进行一次循环操作称为tick,知道了这个之后,对理解这个方法什么时候调用瞬间明白了一些!

再借用别人的例子,加深一下对事件循环的理解吧:

var flag = false // 1. 变量声明Promise.resolve().then(() => {// 2. 将 then 任务分发到本轮循环微任务队列中去console.log('then1') // 8. 执行 then 微任务, 打印 then1,flag 此时是 true 了flag = true
})
process.nextTick(() => {console.log('nextTick1');
})
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// 2. 这个 setTimeout 的回调函数 被加入 宏任务队列中
setTimeout(function () { // 10. 此时微任务队列已经空了(执行完毕),取出并执行下一个宏任务(也就是这个setTimeout 的回调函数)console.log('2'); // 11. 输出 ===> 2// 12. 将此 nextTick 的回调函数 加入 微任务队列process.nextTick(function () { console.log('3');  // 15. 此次宏任务执行完毕,执行微任务队列的第1个微任务, 输出 ===> 3})new Promise(function (resolve) {// 13.执行此Promise中的同步任务,输出 ===> 4console.log('4');resolve();}).then(function () { // 14. 将这个then的回调函数 加入到 微任务队列console.log('5'); // 16. 执行微任务队列的第2个微任务, 输出 ===> 5})
},0)// 3. 将此 nextTick的回调函数 加入 微任务队列
// 7. 第一个宏任务(主执行栈)执行完毕,检查为任务队列,发现非空,按顺序执行其中的任务
process.nextTick(function () {// 8. 执行此微任务(也就是这个回调函数), 输出 ===> 6console.log('6');
})
// 注意Promise构造函数中的代码是同步执行的
new Promise(function (resolve) { // 4. 执行此 Promise 的同步任务,输出 ===> 7console.log('7');resolve();
}).then(function () {   // 5. 将此then的回调函数加入 微任务队列 中// 9. 执行这个 微任务(也就是这个then的回调函数), 输出 ===> 8console.log('8');
})// 6. 将此 setTimeout的回调函数 加入到 宏任务 队列中
setTimeout(function () { // 17. 此时微任务队列再次执行完毕,执行下一个宏任务(也就是这个setTimeout的回调函数)console.log('9'); // 18. 输出 ===> 9// 19. 将此 nextTick 的回调函数 加入 微任务队列process.nextTick(function () { console.log('10'); // 22. 此次宏任务执行完毕,取出第1条微任务并执行,输出 ===> 10})new Promise(function (resolve) {// 20.执行此Promise的同步任务,输出 ===> 11console.log('11');resolve();}).then(function () {   // 21. 将这个then 的回调函数加入到微任务队列中console.log('12');  // 23. 取出第2条微任务并执行,输出 ===> 12})},0)// 执行结果: 1 7 6 8 2 4 3 5 9 11 10 12

再来分析一个简单的例子:

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');})
})
  • 进入主线程,检测到log为普通函数,压入执行栈,输出0;
  • 检测到setTimeOut是特殊的异步方法,交给其他模块处理,其回调函数加入 宏任务(macrotask)队列;
  • 此时主线程中已经没有任务,开始从任务队列中取;
  • 发现微任务队列为空,则取出宏任务队列首项,也就是刚才的定时器的回调函数;
  • 执行其中的同步任务,输出1;
  • 检测到promise及其resolve方法是一般的方法,压入执行栈,输出2,状态改变为resolve;
  • 检测到这个promise的then方法是异步方法,将其回调函数加入 微任务队列;
  • 紧接着又检测到一个promise,执行其中的同步任务,输出4,5,状态改变为resolve;
  • 然后将它的then异步方法加入微任务队列;
  • 执行微任务队列首项,也就是第一个promise的then,输出3;
  • 再取出微任务队列首项,也就是第二个promise的then,输出6;
  • 此时主线程和任务队列都为空,执行完毕;

代码运行结果:

对JavaScript事件循环机制的理解相关推荐

  1. JavaScript事件循环机制

    众所周知JS是一门单线程执行环境的语言,对于同步任务而言,同一时刻只能执行一个任务,后续的任务都要在当前执行的任务后面排队.这种模式在遇到一些执行时间较长的任务的时候就会出问题,会导致页面失去响应.所 ...

  2. 笔试题——JavaScript事件循环机制(event loop、macrotask、microtask)

    今天做了一道笔试题觉得很有意义分享给大家,题目如下: setTimeout(()=>{console.log('A'); },0); var obj={func:function () {set ...

  3. javascript事件循环机制EventLoop

    面试题:1到10 ,每隔一秒输出一个 自执行函数for (var i=1; i<=10; i++) {(function (i) {setTimeout(() => console.log ...

  4. boost log 能不能循环覆盖_前端基础进阶(十四):深入核心,详解事件循环机制...

    Event Loop JavaScript的学习零散而庞杂,很多时候我们学到了一些东西,但是却没办法感受到进步!甚至过了不久,就把学到的东西给忘了.为了解决自己的这个困扰,在学习的过程中,我一直在试图 ...

  5. 浏览器中的事件循环机制

    浏览器中的事件循环机制 网上一搜事件循环, 很多文章标题的前面会加上 JavaScript, 但是我觉得事件循环机制跟 JavaScript 没什么关系, JavaScript 只是一门解释型语言, ...

  6. 对事件循环的一点理解

    最近工作需要学习了解webworker-threads以应对Javascript多线程处理CPU密集型的可能性:参考文档JavaScript多线程之二 Node.js中的Web Worker; 以下是 ...

  7. js事件循环机制(await-async-事件循环)

    await和async 异步函数 async function async关键字用于声明一个异步函数: async是asynchronous单词的缩写,异步.非同步: sync是synchronous ...

  8. 我理解的javascript事件循环(一)

    javascript事件循环分为2种:一种是浏览器端事件循环,一种是node端事件循环. 此文只是捋一捋我对浏览器端事件循环的理解. 前言 我们都知道 JavaScript 是一门单线程语言,这意味着 ...

  9. JavaScript单线程异步的背后——事件循环机制

    感觉这篇文章拖了很久,好尴尬的拖延症 正文从这里开始--- 对JavaScript有个很模糊的印象,它是单线程异步的.本文主要来说说JavaScript到底是怎么运行的.但在这之前,让我们先理一下这些 ...

最新文章

  1. system.out 汉字乱码
  2. 图神经网络(Graph Neural Networks,GNN)综述
  3. glibc和ulibc的区别
  4. Integer 和 int的种种比较
  5. CoreCLR源码探索(六) NullReferenceException是如何发生的
  6. 做梦都在想的游戏设备
  7. c++运动学正反解 ros_朔州智能【机器人关节臂】哪家强
  8. 转载牛人的ASP.NET Cookies简单应用 记住用户名和密码
  9. 路由器配置 之 PAP与CHAP认证
  10. JZOJ5787轨道(容斥+DP)
  11. 《教程》使用STLINK烧录STM32程序
  12. python itchat_Python使用itchat获取微信好友
  13. 侍魂胧月传说服务器维护,侍魂胧月传说4月17日更新维护公告一览
  14. SD卡提示格式化后怎么办?可尝试这种数据恢复方法快速找回!
  15. uniapp 引导页 启动页 闪屏页功能介绍及部分功能实现
  16. Unity3D Animator人物模型下沉的一种原因
  17. Kubernetes--k8s---存活探针和就绪探针的最佳实践
  18. 《通用数据保护条例》(GDPR)系列解读四:出海欧洲必须遵守的七大数据处理原则
  19. Verilog设计_乘法器
  20. std::true_type和std::false_type详解

热门文章

  1. 电脑录屏怎样不录到外界声音?调整这一个开关,即可实现
  2. 支付、登录、第三方登录、强制登录流程图
  3. Linux修改磁盘分区后未写入分区表的解决方法(partprobe)
  4. vue项目token过期
  5. Javascript ES6中数组去重最简便的两种方法(大概)
  6. 周明:预训练模型在多语言、多模态任务的进展
  7. 表情搜索 api数据接口
  8. 支付结果通用通知 php,微信支付-支付结果通用通知
  9. 计算机全国211院校排名2015,2015年全国211大学名单排行榜
  10. C++学习之第十一天-多态、虚函数