JavaScript - Event Loop
单线程
- JavaScript 的主要宿主环境为
浏览器,主要用途是与用户互动和操作 DOM- 如果采用
多线程模型,会带来复杂的同步问题
- 如果采用
- 从
诞生伊始,JavaScript 就是单线程,是其核心特性 - 为了利用
多核 CPU的计算能力,HTML 5提出了Web Worker标准- 允许 JavaScript 创建多个子线程,但
子线程完全受主线程控制,且不允许操作 DOM
- 允许 JavaScript 创建多个子线程,但
任务队列
- JavaScript 在
单线程模式下,所有任务都需要排队 - 任务分类
同步任务(synchronous)- 在
主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
- 在
异步任务(asynchronous)- 不进入主线程,而是进入
任务队列的任务 - 只有任务队列
通知主线程,某个异步任务可以执行了,该异步任务才会进入主线程执行
- 不进入主线程,而是进入
- 运行机制
- 所有
同步任务都在主线程上执行,形成一个执行栈 - 主线程之外,还存在一个
任务队列- 只要
异步任务有了运行结果,就会在任务队列中放置一个事件
- 只要
- 一旦
执行栈中所有的同步任务执行完毕,系统就会读取任务队列中的事件事件对应的异步任务会结束等待状态,进入执行栈开始执行
- 主线程不断重复上一步骤(只要
主线程空闲,就会去读取任务队列)
- 所有
Event + Callback
任务队列是一个事件的队列异步任务完成后会向任务队列添加一个事件:表示相关的异步任务可以进入执行栈了- 异步任务本身不需要 CPU 参与,如
IO(DMA) - 但异步任务对应事件的
回调函数需要占用CPU时间
- 异步任务本身不需要 CPU 参与,如
主线程读取任务队列,实际上就是读取事件任务队列中的事件:IO 事件、鼠标点击、页面滚动- 只要为
Event指定过Callback,当这些Event发生时就会进入任务队列,等待主线程读取
- 只要为
Callback:被主线程挂起的代码– 需要占用 CPU 时间异步任务必须指定Callback- 当
主线程开始执行异步任务,实际执行的就是对应的Callback
任务队列是一个FIFO的数据结构:排在前面的事件,优先被主线程读取- 主线程的读取过程
基本上是自动的- 主要
执行栈一清空,任务队列上第1位的事件会自动进入主线程 - 针对
定时器,主线程会首先检查下执行时间,某些事件只有到了规定时间,才能返回主线程
- 主要
- 主线程的读取过程
Event Loop
event loop:主线程循环不断地从任务队列中读取事件
- 主线程运行时,会产生
Heap和Stack Stack中的代码调用各种外部 API,在任务队列中加入各种事件- 只要
Stack中的代码执行完毕- 主线程就会去读取
任务队列(Callback Queue),依次执行那些事件对应的回调函数
- 主线程就会去读取
- 执行栈中的代码(
同步任务),总是在读取任务队列(异步任务)之前执行
1 | let request = new XMLHttpRequest(); |
定时器
任务队列除了放置异步任务的事件,还可以放置定时事件(指定某些代码在多少时间后执行)定时器功能主要由setTimeout(一次性) 和setInterval(周期性) 来完成,内部运行机制完全一样
1 | // 1 |
setTimeout(fn, 0):尽可能早地执行
往任务队列尾部添加一个事件,等到同步任务和任务队列现有的事件都处理完后,才会得到执行
setTimeout: 只是将事件插入到任务队列
必须等到执行栈执行完,主线程才会去执行它指定的Callback
如果
执行栈耗时很长,此时无法保证回调函数一定会在setTimeout指定的时间执行
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.













