js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)
javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步。
javascript事件循环
事件循环中的同步任务,异步任务:
同步和异步任务在不同的执行"场所",同步的进入主线程,异步的进入Event Table执行并注册函数。
当指定的异步事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,推入主线程执行。
js引擎的monitoring process进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。上述过程会不断重复,也就是常说的Event Loop(事件循环)。
用个例子说明上述过程:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
ajax(异步任务)进入Event Table,注册回调函数success。
执行console.log('代码执行结束')。(同步任务在主线程执行)
ajax事件完成,回调函数success进入Event Queue。
主线程从Event Queue读取回调函数success并执行。
setTimeout和setInterval中的执行时间
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},3000); //先执行这里
// ... 3s later 3秒到了,计时事件timeout完成,定时器回调进入Event Queue,主线程执行已执行完,此时执行定时器回调。
// 执行啦
setTimeout明明写的延时3秒,实际却5,6秒才执行函数,这咋回事?
setTimeout(() => {
task()
},3000) sleep(10000000)
上述代码在控制台执行task()需要的时间远远超过3秒,执行过程:
task()进入Event Table并注册,计时开始。
执行sleep函数,很慢,非常慢,计时仍在继续。
3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
sleep终于执行完了,task()终于从Event Queue进入了主线程执行。
上述的流程走完,setTimeout这个函数是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。
重点来了:定时器的毫秒,不是指过ms秒执行一次fn,而是过ms秒,会有fn进入Event Queue。
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。
对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。
宏任务和微任务
除了广义的同步任务和异步任务,我们对任务有更精细的定义:
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。
在宏任务和微任务概念中的事件循环机制:
主任务(宏任务)完——所有微任务——宏任务(找到宏任务其中一个任务队列执行,其中如果又有微任务,该任务队列执行完就执行微任务)——宏任务中另外一个任务队列(里面偶微任务就再执行微任务)。
总的来说就是在宏任务和微任务之间来回切。下面列子执行过程:
第一轮:主线程输出:【1,7】,添加宏任务【set1,set2】,添加微任务【6,8】。执行完主线程,然后执行微任务输出【6,8】
第二轮:执行宏任务其中一个任务队列set1:输出【2,4】,执行任务的过程,碰到有微任务,所以在微任务队列添加输出【3,5】的微任务,在set1宏任务执行完就执行该微任务,第二轮总输出:【2,4,3,5】
第三轮:执行任务另一个任务队列set2:输出【9,11】,执行任务的过程,碰到有微任任务,所以在微任务队列添加输出【10,12】的微任务,在set2宏任务执行完就执行该微任务,第三轮总输出:【9,11,10,12】
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)
console.log('1'); //第一轮主线程【1】 setTimeout(function() { //碰到set异步,丢入宏任务队列【set1】:我将它命名为set1
console.log('2');//第二轮宏任务执行,输出【2】
process.nextTick(function() {//第二轮宏任务执行,碰到process,丢入微任务队列,【3】
console.log('3');
})
new Promise(function(resolve) {//第二轮宏任务执行,输出【2,4】
console.log('4');
resolve();
}).then(function() {
console.log('5')//第二轮宏任务执行,碰到then丢入微任务队列,【3,5】
})
})
process.nextTick(function() { //碰到process,丢入微任务队列【6】
console.log('6'); //第一轮微任务执行
})
new Promise(function(resolve) {
console.log('7'); //new的同时执行代码,第一轮主线程此时输出【1,7】
resolve();
}).then(function() {
console.log('8') //第一轮主线程中promise的then丢入微任务队列,此时微任务队列为【6,8】。当第一轮微任务执行,顺序输出【6,8】
}) setTimeout(function() { //碰到set异步丢入宏任务队列,此时宏任务队列【set1.set2】:我将它命名为set2
console.log('9');//第三轮宏任务执行,输出【9】
process.nextTick(function() { //第三轮宏中执行过程中添加到微任务【10】
console.log('10');
})
new Promise(function(resolve) {
console.log('11');//第三轮宏任务执行,宏任务累计输出【9,11】
resolve();
}).then(function() {
console.log('12') //第三轮宏中执行过程中添加到微任务【10,12】
})
})
参考文章:
js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)的更多相关文章
- JS JavaScript事件循环机制
区分进程和线程 进程是cpu资源分配的最小单位(系统会给它分配内存) 不同的进程之间是可以同学的,如管道.FIFO(命名管道).消息队列 一个进程里有单个或多个线程 浏览器是多进程的,因为系统给它的进 ...
- Node.js 的事件循环机制
目录 微任务 事件循环机制 setImmediate.setTimeout/setInterval 和 process.nextTick 执行时机对比 实例分析 参考 1.微任务 在谈论Node的事件 ...
- 浏览器中 JS 的事件循环机制
目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...
- JS浏览器事件循环机制
文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star. 先来明白些概念性内容. 进程.线程 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的. ...
- JS:事件循环机制、调用栈以及任务队列
点击查看原文 写在前面 js里的事件循环机制十分有趣.从很多面试题也可以看出来,考察简单的setTimeout也就是考察这个机制的. 在之前,我只是简单地认为由于函数执行很快,setTimeout执行 ...
- js的事件循环机制和任务队列
上篇讲异步的时候,提到了同步队列和异步队列的说法,其实只是一种形象的称呼,分别代表主线程中的任务和任务队列中的任务,那么此篇我们就来详细探讨这两者. 一.来张图感受一下 如果看完觉得一脸懵逼,请继续往 ...
- JS基础-事件循环机制
从一道题浅说 JavaScript 的事件循环 原文链接: https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7 ...
- js事件循环机制辨析
对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...
- JS 事件循环机制 - 任务队列、web API、JS主线程的相互协同
一.JS单线程.异步.同步概念 从上一篇说明vue nextTick的文章中,多次出现“事件循环”这个名词,简单说明了事件循环的步骤,以便理解nextTick的运行时机,这篇文章将更为详细的分析下事件 ...
随机推荐
- 如何判断app的页面是原生的还是H5的webview页面
1.看布局边界(在手机侧观察) 开发者选项->显示布局边界,页面元素很多的情况下布局是一整块的是h5的,布局密密麻麻的是原生控件.页面有布局的是原生的,否则为h5页面.(仅针对安卓手机试用)如下 ...
- 在Mac OS X中完善PHP环境:memcache、mcrypt、igbinary
本文环境: Mac OS X 10.8.5 Xcode 5.0 Mac OS X升级到10.8.5之后,内置的Apache升级到2.2.24,PHP升级到了5.3.26.本文以此环境为基础. 本文简介 ...
- Linux soft lockup分析
关键词:watchdog.soft lockup.percpu thread.lockdep等. 近日遇到一个soft lockup问题,打印类似“[ 56.032356] NMI watchdog: ...
- GIT归纳整理
1. 将repo_a的分支提交到repo_b分支 repo_a:表示原始git库地址:repo_b:表示新增的git库地址. git remote add new_remote repo_b:new_ ...
- 基于Metronic的Bootstrap开发框架--工作流模块功能介绍
在很早之前的随笔里面,已经介绍了WInform框架中工作流模块的功能,不过由于工作流模块中界面处理部分比较麻烦,一直没有在Bootstrap框架中进行集成,最近由于项目的关系,花了不少精力,把工作流模 ...
- 让linux启动更快的方法
导读 进行 Linux 内核与固件开发的时候,往往需要多次的重启,会浪费大把的时间. 在所有我拥有或使用过的电脑中,启动最快的那台是 20 世纪 80 年代的电脑.在你把手从电源键移到键盘上的时候,B ...
- 《Effective C++》继承与面对对象设计:条款32-条款40
条款32:确定你的public继承塑模出is-a关系 public继承意味着is-a.适用于base class身上的每一个函数也一定适用于derived class. 条款33:避免遮掩继承而来的名 ...
- Unit 4.css的导入方式和选择器
一.什么是css CSS是指层叠样式表(Cascading Style Sheets),样式定义如何显示HTML元素,样式通常又会存在于样式表中.也就是说把HTML元素的样式都统一收集起来写在一个地方 ...
- Linux(Ubuntu)使用日记(七)------终端控制器Terminator安装使用
1.目的 实现分屏效果,如图: 如果使用系统自带的终端,可能会使这种效果: 综上所述,知道我们为什么要安装Terminator了吧. 2.安装过程 Terminator 的安装非常方便,在 Ubunt ...
- jdk安装及配置
点击jdk文件运行 安装完成后的目录: 2,在系统变量下面配置 JAVA_HOME:你自己的jdk的路径 CLASSPATH= .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME% ...