银行排队中出EventLoop
日常当中,我们有没有经历过这么一种情况,去银行柜台排队办理业务,打算办理的业务当中分一个最主要的事情,还可能有若干个其它没那么重要的业务,等我们彻底办理完毕,就轮到下一个排队的人,你可以想象这其实就是一个eventloop吗?
前言
本文讲述的内容包括以下:
- 同步模式与异步模式
- 宏任务与微任务及任务队列
- 银行排队中出EventLoop
对EventLoop已经比较理解透彻对本文还感兴趣的的小伙伴可以直接从目录跳到最后直接观看银行排队中出EventLoop
同步模式与异步模式
JavaScript为什么是单线程
首先我们都知道JavaScript是单线程的,所以才有两种任务模式:同步任务(synchronous),另一种是异步任务(asynchronous),那么至于为什么被被设计成单线程,引用阮一峰老师的描述概括起来就一句话:
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
详细描述可以看原文:为什么JavaScript是单线程?
同步模式
官方说明:
所谓"单线程/同步模式",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
你可以理解为有一个人(且叫他B),它需要做很多件事情: 包括:
- 买菜
- 做饭
- 洗碗 没有人帮助他,并且每一件事情执行的前提是必须上一件事情执行完毕之后才能执行;
如图:
代码层面:
console.log('begin')
function bar(){
console.log('bar task')
}
function foo(){
console.log('foo task')
bar()//同样,执行bar内存地址的代码console.log('bar task')
}
foo() //可以理解为执行该内存地址的代码,也就是console.log('foo task')及bar()
console.log('end')
foo()
//输出结果
//begin
//foo task
//bar task
//end
异步模式
官方说明:
"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。
这也是为什么JS单线程需要有异步这种模式所在,试想一下,要是只有同步模式,执行某一些耗时任务太久就会形成堵塞,后面的任务都执行不了,这就会给用户非常不好的体验,甚至卡死
你可以理解为有一个人(且叫他A),它需要做很多件事情: 包括:
- 买菜
- 做饭(不熟悉,不知道多久)
- 学习
按照同步模式来去做这些任务,我们不知道做饭要做多久,可能这个饭做1个小时,2个小时,甚至更久,那要是卡在做饭这里我们不就不用学习了?这明显是不可以的,所以这个时候我们需要一个外援, 干脆就叫B:
- 我们买好菜
- 把做饭的任务交给B去做
- 然后我们就可以学习了
- B做好饭跟我们说,那原定的计划就完成了
如图:
代码层面:
console.log('买菜')
setTimeout(bar,0) // setTimeout就是一个异步API,相当于上文提到的那个B,而bar就是我们要求B帮我们做饭这个任务,是个回调函数
function bar(){
console.log('做饭')
}
console.log('学习')
//输出结果
//买菜
//学习
//做饭
总结
- JavaScript的运行模式就是单线程/同步模式
- 运行环境提供的API是以同步或异步模式的方式工作
- 所以我们一般说的模式指的是API的工作方式
- console.log就是同步API
- setimeout就是异步API 更多相关描述可以参考阮一峰的文章Javascript异步编程的4种方法
宏任务与微任务及任务队列
任务队列
在上文中我们已经大概了解了同步跟异步的区别;
官方说明:
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
有点绕,下面我们会更加详细的解读; 暂且我们只需要把任务队列理解为把等待执行的任务按顺序放在一个队伍就可以了
宏任务与微任务
宏任务(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。 微任务microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
| 宏任务(macrotask) | 微任务(microtask) |
|---|---|
| script (可以理解为JS第一次运行同步代码) | Promise.then |
| setTimeout/setInterval | Object.observe |
| UI rendering/UI事件 | MutaionObserver |
| postMessage,MessageChannel | process.nextTick(Node.js 环境) |
| setImmediate,I/O(Node.js) | ... |
EventLoop
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如果宿主为浏览器,可能会渲染页面;
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
在上诉tick的基础上需要了解几点:
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
可以说上述的几个部分都是为了最后这个大家更好的理解而服务的;
EventLoop:
说句实话,真的不好理解,第一次看都不知道它想表达什么;
银行排队中出Eventloop
看到这副图我想应该会有大概的的一个感觉了,我们再替换一下,把主要任务替换为宏任务,次要任务替换为微任务,并把里面的任务替换为API,再把柜员办理任务的流程按照流程图来展示
结合执行栈调用的情况流程图:
对应关系:
官方表达:
- 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如果宿主为浏览器,可能会渲染页面;
- 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
银行实例:
- 柜员给每个排队的人来先处理主要任务:办银行卡,直至办理完毕
- 柜员再看看还是否有什么次要任务需要办理,并按顺序办理储值->查询余额,直至次要任务清空
- 结束这个人的业务办理,轮到下一个
总结
- 每一次事件循环就相当于我们跟银行柜员对接业务的过程
- 先执行宏任务,再判断是否有微任务队列,有则根据微任务队列的微任务按顺序执行,没有则执行下一个宏任务
最后的最后
如果能给大家一些启发或者感悟,希望大家点个赞,如有表达不清或者错误的地方可以指出并且下方留言,感谢