目录
前言
有人称Event Loop为事件循环机制,而我更愿意将其解释为事件轮询机制,在之后的内容中你会感受到这一点的区别在哪里。说是事件轮询机制,我们也可以说是任务轮询机制,因为英文是Event Loop,所以我们在此文中将其翻译为事件轮询。
阅读本文之前,首先对JavaScript的单线程和异步要有一定的了解
ECMA只负责指定标准,Event Loop如何实现,它并不关心。
本文在概念的结构顺序上参考了阮一峰老师的博客《再谈javascript的运行机制: Event Loop》,理解也大多源于此文,加上个人看法,以更容易让读者理解的方式表述出来。
在讲JavaScript的事件轮询机制之前,让我们先来了解几个重要的概念:
任务队列
我们知道,由于JavaScript是单线程,这意味着所有任务都要排队等待执行,后面的任务要等待前面的任务执行结束才能开始执行,如果前一个任务耗时比较久的话,后一个任务就必须一直等待。
CPU的运算能力往往是过剩的,我们等待的时间主要是IO操作的时间,这时候会发现,有时候javascript的主线程完全可以不管这些IO操作的任务,我们可以先将这些任务挂起,执行后面的任务,等到IO操作返回了结果,再将之前挂起的任务继续执行。
根据上述的情况,我们大致可以将这分为两种任务:同步任务(synchronous) 和 异步任务(asynchronous)。同步任务指主线程上排队等待被执行的任务,这些任务在一个执行栈中,顺序等待被执行,在主线程上的任务需要等待前一个任务执行结束才能被执行;异步任务是指那些被主线程挂起的任务,异步结果返回后一个事件被子线程放入“任务队列”中等待被读取进入主线程,而不是直接进入到主线程中等待被执行,这一点是要注意也是我们要着重强调的。而什么时候才会执行任务队列中的任务呢?我们先来看一张图:
捋一下整个流程:
步骤1: 首先解析JavaScript代码,这时代码未执行,将同步任务放到执行栈中等待被执行;
步骤2: 对执行栈进行判断,如果执行栈不为空则逐个执行排队的任务(任务执行过程中产生的异步任务抛给子线程进行处理,当任务结果返回时将一个事件放入任务队列中等待被读取);如果执行栈为空,则读取任务队列中可执行的事件,将其放到执行栈中等待被执行;
步骤3: 不断重复步骤2的操作。
注:任务队列也是一个先进先出的栈,先进入任务队列的任务会先被读取到执行栈中等待执行
注意:当执行栈空了,才会去读取任务队列,这个过程会不断重复。 这就是JavaScript的运行机制,没有我们想象的那么什么和复杂,对吧!
事件的概念
“任务队列"是一个事件的队列(也可以理解成消息的队列),异步操作完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列”,就是读取里面有哪些事件。
“任务队列"中的事件,还包括鼠标点击、键盘点击,定时操作等等。只要指定过回调函数,这些事件发生时就会进入"任务队列”,等待主线程读取。
回调函数
在讲事件轮询机制之前,我们还要了解一件事情,我们发起异步任务的目的是什么?是希望获得需要的结果,然后根据这个结果去做一些事情对吧,如果异步任务结果返回了,而我们什么都不做的话,就失去了发起异步任务的初衷!我们所谓的主线程挂起的任务,实际上是一段待执行代码,在异步结果或者说是状态返回时,我们执行这段代码,也就是执行异步任务;而这里的代码,我们称之为回调函数。
事件轮询机制Event Loop:
终于讲到在任务队列一章中我们放了一张图,主线程从"任务队列"中读取事件,这个过程是循环不断的,故此,我们将其称为Event Loop(直译为事件循环),照例,先摔一张图在这里:
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,error)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。在这里我们能够更明确的看出任务队列是一个先进先出的数据结构。
这就像是每次执行栈为空时,便会询问任务队列是否有可执行任务,这也是我文章开头时为什么将Event Loop解释为事件轮询机制。
注:本文没有详细区分“任务队列“中的情况,之后会详解“任务队列”中的不同情况。
结语
此篇主要是讲解了JavaScript的运行机制,后面我们会从代码层面来深入分析JavaScript在代码层面的体现。
希望此文能够解决大家工作和学习中的一些疑问,避免不必要的时间浪费,有不严谨的地方,也请大家批评指正,共同进步!
转载请注明出处,谢谢!
本面试题为前端常考面试题,后续有机会继续完善。我是歌谣,一个沉迷于故事的讲述者。
欢迎一起私信交流。