起因
即使我完全没有系统学习过JavaScript的事件循环机制,在经过一定时间的经验积累后,也听过一些诸如宏任务和微任务、JavaScript是单线程的、Ajax和Promise是一种异步操作、setTimeout会在最后执行等这类的碎片信息,结合实际的代码也可以保证绝大多数情况下代码是按照我希望的顺序执行,但是当我被实际问到这个问题时,发现自己并不能切实地理解这其中的原理,相关的资料有很多,但还是要用自己的理解来表述一遍。
为什么要有事件循环?
首先是个简单的问题,换句话说就是事件循环有什么作用,我为什么要学习这个知识?就像第一段里提到的,众所周知JavaScript是单线程语言,但这并不代表JavaScript不需要异步操作,反向思考一下,如果你所写的所有Ajax操作都是同步的会有什么后果:我们每次向服务端发送请求,整个页面都会因此停滞,直到请求返回,无论响应时间是1毫秒、1秒还是1分钟。对于用户体验来说,这无疑是灾难,所以JavaScript提供了各种异步编程的方式:事件循环、Promise、Generator、Worker等,这里我们还是把目光先聚焦到事件循环上,随着问题的深入,我们会知道事件循环为我们解决了什么问题。
事件循环是怎样运作的?
要理解这个问题,推荐先看下这个视频:到底什么是Event Loop呢?,然后是视频中提到的网站:loupe,结合视频我们可以很形象地看到事件是如何在循环中运作的,网站则是根据输入的代码来用动画演示这个过程。
顺着视频的思路我们把JavaScript的执行分成几部分:调用栈(Call stack)、事件循环(Event loop)、回调队列(Callback queue)、其他API(Other apis)。
调用栈
因为JavaScript是单线程的,所以只能一句一句地执行我们的代码,编译器每读到一个函数就把它压入栈中,栈顶的函数返回结果时就弹栈,在这个过程中只有同步函数函数会进入调用栈走正常的执行流程,而setTimeout
和Promise
这种异步函数则会进入回调队列,形成事件循环的第一步。
Web API
视频中最令我感到意外的是很多我们熟悉的函数并不是JavaScript提供的,而是来自于Web APIs,比如Ajax、DOM、setTimeout等,这些方法的实现并没有出现在V8的源码中,因为它们是由浏览器提供的,更准确地说,应该是运行环境提供的,因为JavaScript的运行环境并不是统一的,不同的浏览器核心就不说了,我们就分成浏览器和Node就可以,看似与我们讨论的事件循环无关,但其中还是存在区别,这个问题我们放在后面说明。