『前端干货篇』: 你不知道的Event Loop
栏目: JavaScript · 发布时间: 7年前
内容简介:一星期的满课,身心疲惫(×_×)...周末闲下来,仔细研究了下JS的事件轮询机制,看了看阮一峰大大的相关文章,真的收货挺多。相信这道题很多人都看过,结果是先输出在聊Event Loop之前,有必要先讲讲JS的一些重要特点
一星期的满课,身心疲惫(×_×)...周末闲下来,仔细研究了下JS的事件轮询机制,看了看阮一峰大大的相关文章,真的收货挺多。
从一道面试题说起
setTimeout(function() {
console.log(111);
}, 0); // 这里定时器时间设置为0ms后执行
console.log(222);
复制代码
相信这道题很多人都看过,结果是先输出 222 ,再输出 111
可能新手会犯错,认为定时器设置0毫秒就等于立即就执行,所以先输出 111 。但其实内部涉及一个很重要的JS运行机制,也就是我们今天的主角——事件轮询(Event Loop)
JS的特点
在聊Event Loop之前,有必要先讲讲JS的一些重要特点
JS的单线程
JS的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JS不能有多个线程呢?
第一,为了提高效率,减少CPU的开销。在多线程中,CPU需要来回切换线程,就会存在线程切换上的开销。
第二,JS最初设计时,是作为浏览器的脚本语言,主要用途是与用户互动,以及操作DOM。这就决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JS同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
JS的异步
说到JS的异步,可能有同学会问啦,JS是单线程的怎么还能异步执行,这不是自相矛盾吗?的确,单线程和异步确实不能同时成为一个语言的特性,所以它本身不可能是异步的。一定是存在一种机制让它能够异步执行,往下看!
任务队列
JS是单线程就意味着,所有任务需要排队,等前一个任务结束,才能执行后一个任务。但前端的某些任务是非常耗时的,例如IO设备(输入输出设备)、Ajax操作(从网络读取数据)、定时器...不得不等着结果出来,再往下执行。如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死,用户体验很差。
这个时候,任务队列就派上用场了。
在JS中,所有任务可以分成两种。一种是同步任务,另一种是异步任务。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
任务队列中的任务事件,一般有个共性就是存在"回调函数"。所谓"回调函数",就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务时,执行就是对应的回调函数。
值得一提的是,任务队列不止一条。由于异步任务有很多种,比如事件监听类,定时器类,Ajax请求类...所以可以有很多条任务队列
这样说大家可能还不太明白,我画个图解释下
Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件轮询)。
执行流程
(1)所有同步任务都在主线程上执行,形成一个执行栈(每执行一条代码,向栈中压入这条代码)。 (2)主线程之外,还存在一个"任务队列"。存放异步执行的代码,如定时器、事件监听回调函数等,进入等待状态。 (3)一旦主线程中的所有同步任务执行完毕,就会读取"任务队列",看看里面有哪些任务。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 (4)主线程不断重复上面的第三步(轮询)。 复制代码
具体举个例子吧
假如我们有一段代码
var a = 11111
console.log(a)
var btn1 = document.getElementById('btn1')
btn1.onclick = function() {
console.log(22222)
}
var btn2 = document.getElementById('btn2')
btn2.onclick = function() {
console.log(33333)
}
setTimeout(function() {
console.log(44444)
}, 1000)
console.log(55555)
复制代码
以上代码在JS引擎中其实是这样执行的
var a = 11111
console.log(a)
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
console.log(44444)
复制代码
这五句代码是同步代码,会直接进入主线程,依次执行
btn.onclick = function() {
console.log(22222)
}
btn2.onclick = function() {
console.log(33333)
}
setTimeout(function() {
console.log(33333)
}, 1000)
复制代码
这三块异步代码不会直接进入主线程,而是先在相应的任务队列中注册
当主线程执行完所有同步代码时,就开始不断轮询任务队列是否有任务需要执行,轮询的过程很快。在轮询过程中,要是用户点击了btn1按钮,任务队列会通知主线程,"说我这有异步代码已就绪,需要你来执行"。这时btn1.onclick就从任务队列中弹出,到主线程中执行
同样的,当过了1s时,任务队列会通知定时器需要执行,这时主线程轮询时得到这条"通知",所以就执行定时器中语句
知道这个机制后,我们再回头看看那个面试题
setTimeout(function() {
console.log(111);
}, 0); // 这里定时器时间设置为0ms后执行
console.log(222);
复制代码
这里的 console.log(222) 首先在主线程中执行,而定时器则是先在任务队列中注册。当主线程中代码执行完(也就是 console.log('222') 这条语句执行完后),主线程开始轮询任务队列中的异步代码,由于定时器设置的时间是0ms,所以任务队列会立即通知主线程,可以执行。最后定时器就会到主线程中开始执行。这就是为什么打印的结果先是222,后111。
总结
JS的事件轮询的机制,使任务队列、JS主线程、异步操作之间可以相互协作。这正是JS语言与众不同的运行方式,也因此使它具备了其他语言不具备的优势。
最后感谢大家百忙之中辛苦观看,也希望这篇文章可以帮助屏幕前的你更好的理解JS的Event Loop机制!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 前端面试题干货集合
- 干货| 60G资源【Web前端】转行前端必需品
- 前端速来!2017年6月前端开发者干货大合集
- 『前端干货篇』:你不知道的Stylus
- 干货!前端 Code Review 的最佳实践方案
- 硬核干货:葡萄城 SpreadJS 前端表格技术分享
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Trading and Exchanges
Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00
This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!