理解javascript事件循环机制
发布于 1 年前 作者 zhangzhi 238 次浏览 来自 分享

Event Loop(事件轮询)机制是一个经常把人搞晕的东东。我不敢说我完全明白,只是在此谈谈我的浅见。

事件的处理

浏览器是一个事件驱动(event-driven)架构的软件。它的UI线程中会不断产生用户事件。但是处理事件的JavaScript是单线程执行的,这是一个浏览器环境下难以改变的现状(HTML5 Web Works没有从本质上改变这个模型)。这意味着:在JavaScript处理某个任务(执行某段代码)过程中,如果产生了用户事件,它不会立即被处理。那这种情况该怎么办呢?

浏览器维护了一个“任务队列”(一个优先队列数据结构),它是一个浏览器进程资源。每当UI线程产生一个事件,事件对象就被当做任务放入任务队列中(enqueue)。当JavaScript执行线程空闲的时候,队列中的一个任务就会被送往JavaScript执行线程(dequeue),进行相应的处理。

这个enqueue和dequeue的机制就是“Event Loop”。

但是,不仅用户事件可以被Event Loop机制处理,还能更多的东西是依赖这个机制的。

异步IO的处理

如果没有异步的理念,这个世界会完全不同:一个耗时的I/O操作(例如HTTP请求)会导致JavaScript执行线程等待,而后续的操作得不到执行。这种情况下,一个耗时的服务器端数据库操作http请求,会让JavaScript执行线程阻塞,浏览器将长期处于假死状态,在此期间,其他后续操作(包括用户的交互事件)得不到响应。

好在浏览器不是单线程的。它可以(但不是必须)让这些I/O任务让其他线程来托管,这样就形成了一个执行任务的线程池。但是这些任务的结果总归要回到JavaScript执行线程上处理,于是这些任务也被放到任务队列中:需要被托管的任务被放入队列中(enqueue),已完成的任务会被从队列中一个个取出(dequeue),回到JavaScript执行线程执行回调。在这些耗时的I/O任务被托管的时候,JavaScript执行线程可以执行其他代码。

在Node中,这个过程是类似的。本文不表。

这便是异步的原理了。我们看到它同样依赖Event Loop的机制。

定时器

浏览器的全局对象window提供了两个方法,setTimeout和setInterval。这两个方法其实是调用了浏览器的API,将一个任务移除出JavaScript执行线程中,延时处理。

我们现在马上可以反应过来:这个将要被延时的任务同样是放到了任务队列中。在一次Event Loop过程中,它会优先将该时间点下已经到时的延时任务移除出队列,放入JavaScript执行线程中。这意味着,任务队列是一个优先队列。

但是由于JavaScript执行线程的执行时间是不确定的,所以这个延时只是一个大体的值,它取决于JavaScript执行线程的执行时间。

回调函数

任务完成的时候,JavaScript需要执行哪段代码来处理呢?当然是回调函数了。

但是不免奇怪的一点就是:JavaScript中怎么知道要执行的是哪个回调函数呢?答案就是:任务被放入任务队列的时候,该任务的回调函数会被注册(注册到什么地方?需要进一步探究)。这样,当特定任务完成的时候,任务结果和回调标记会返回给JavaScript执行线程,进入执行栈。

事件处理器

与其他任务不同,事件并不是由JavaScript执行线程发出的,而是从UI线程中发出的。

事件处理器和回调函数类似。但是特定的事件处理器在浏览器进入异步事件驱动阶段时就会针对特定的事件注册。当事件对象返回到JavaScript执行线程时,事件处理器也会同时进入执行栈中执行。

结束

越往后写,越发现我之前的一些理解有偏差。在学习过程中,我也要多反思,多总结。之前写的不对的地方,我也会尽早纠正。

回到顶部