资讯专栏INFORMATION COLUMN

『前端干货篇』: 你不知道的Event Loop

mykurisu / 2016人阅读

摘要:任务队列中的任务事件,一般有个共性就是存在回调函数。存放异步执行的代码,如定时器事件监听回调函数等,进入等待状态。总结的事件轮询的机制,使任务队列主线程异步操作之间可以相互协作。

从一道面试题说起
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(55555)

这五句代码是同步代码,会直接进入主线程,依次执行

btn.onclick = function() {
    console.log(22222)
}

btn2.onclick = function() {
    console.log(33333)
}

setTimeout(function() {
  console.log(44444)
}, 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机制!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100757.html

相关文章

  • 优秀文章收藏(慢慢消化)持续更新~

    摘要:整理收藏一些优秀的文章及大佬博客留着慢慢学习原文协作规范中文技术文档协作规范阮一峰编程风格凹凸实验室前端代码规范风格指南这一次,彻底弄懂执行机制一次弄懂彻底解决此类面试问题浏览器与的事件循环有何区别笔试题事件循环机制异步编程理解的异步 better-learning 整理收藏一些优秀的文章及大佬博客留着慢慢学习 原文:https://www.ahwgs.cn/youxiuwenzhan...

    JeOam 评论0 收藏0
  • 【回顾九月份第二周】 前端你该知道事儿

    摘要:顺便一说,这首歌的原唱是秋田,中岛当年嗓子坏了,才有这歌。中文是直接翻译来的,作曲是秋田。一部电影春夏秋冬又一春春夏秋冬又一春是由金基德执导,金英民吴英秀金基德主演的一部韩国电影。年月日于韩国上映。 原链接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...

    sixgo 评论0 收藏0
  • 【回顾九月份第二周】 前端你该知道事儿

    摘要:顺便一说,这首歌的原唱是秋田,中岛当年嗓子坏了,才有这歌。中文是直接翻译来的,作曲是秋田。一部电影春夏秋冬又一春春夏秋冬又一春是由金基德执导,金英民吴英秀金基德主演的一部韩国电影。年月日于韩国上映。 原链接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...

    levius 评论0 收藏0
  • 前端资源收集整理

    摘要:工作原因,最近一年断断续续写了一点前端代码,收集整理了一些资料,和大家共享。 工作原因,最近一年断断续续写了一点前端代码,收集整理了一些资料,和大家共享。 Github版本:Front-End Resource Collection 前端相关资源汇总 学习指导 精华文章 Web前端的路该怎么走?:文章超长,但是干货超级多,值得反复精读! 听说2017你想写前端?:适合于已经度过了小白阶...

    awesome23 评论0 收藏0

发表评论

0条评论

mykurisu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<