JavaScript是单线程的
- 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题
- H5提出Web Worker标准, 允许JavaScript脚本创建多个线程, 但是子线程完全受主线程控制, 不得操作DOM
任务队列
基于JavaScript是单线程的核心特征, 为了优化任务处理流程, 设计者将所有任务分为两种: 同步任务(synchronous)和异步任务(asynchronous)
同步任务
- 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务
- 不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
- 宏任务(macrotask)
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
- 微任务(microtask)
- process.nextTick
- promise
- MutationObserver
- 异步执行的运行机制:
- 主流程和任务队列的示意图:
Event Queue(任务队列)是一个事件的队列
事件和回调函数
事件
IO设备完成一项任务, 就在Event Queue中添加一个事件, 表示相关的异步任务可以进行“执行栈”.主线程读取Event Queue就是读取里面有哪些事件
回调函数(Callback)
被主线程挂起的代码. 异步任务必须制定回调函数, 当主线程开始执行异步任务, 就是执行对应的回调函数
任务队列(Event Queue)
一个先进先出的数据结构
- 主线程的读取基本都是自动的, 执行栈已清空, 任务队列上的第一位事件就自动进入主线程, 但主线程会检查执行时间(定时器)
Event Loop
主线程从任务队列中读取事件, 这个过程是循环不断的, 所以整个运行过程又称为Event Loop(事件循环)
- Event Loop示意图
主线程运行时, 产生堆(Heap)和栈(Stack), 栈中的代码调用外部API, 在任务列表中加入各种事件. 只要栈中的代码代码执行完成, 主线程就会去读取任务队列, 依次执行那些事件所对应的回调函数
定时器
setTimeout()
- 一次性执行
- 指定某个任务在主线程最早可得的空闲时间执行. 即在现有任务队列的尾部添加事件
- H5标准规定setTimeout()第二个参数的最小值(最短间隔)不得低于4毫秒, 如果低于会自动增加
- setTimeout()的执行时间并不一定在指定的时间执行, 执行时间取决于执行栈执行完的时间
setInterval()
- 重复执行
Node.Js的Event Loop
- Node.Js的运行机制不同于浏览器环境
- 运行示意图:
- 运行机制:
特性:
process.nextTick
- 在触发下一次事件轮询(读取Event Queue)前触发回调函数
- 同一轮Loop下, nextTick总在setTimeout之前触发回调
- 无论在一个函数内嵌套多少个nextTick, 都会在当前执行栈中完成
setImmediate
- 在当前任务队列的尾部添加事件, 类似于setTimeout
- 与setTimeout同时使用时, 无法确定哪个先被触发回调
JavaScript执行机制
- 主线程任务 -> 微任务 -> 宏任务
- 同级下, 微任务优先于宏任务执行