javascript与nodejs的事件环
nodejs的event是基于libuv,而浏览器的event loop则在html5的规范中明确定义。
浏览器中的事件环
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
- 主线程从任务队列中读取事件,这个过程是循环不断的
console.log(1)
setTimeout(function(){
console.log(2)
setTimeout(function(){
console.log(3)
})
})
setTimeout(function(){
console.log(4)
})
console.log(5)
- 打印结果:15243
- 存放在任务队列中的事件有:onclick,onload,ajax,setTimeout,setInterval等异步方法
- 执行栈中的方法总是在任务队列前执行,任务队列中的执行顺序是先进先出
nodejs中的事件环
Node.js也是单线程,但是它的event loop运行机制不同于浏览器环境。
- 我们写的js代码会交给v8引擎进行处理
- 代码中可能会调用nodeApi,node会交给libuv库处理
- libuv通过阻塞i/o和多线程实现了异步io
- 通过事件驱动的方式,将结果放到事件队列中,最终交给我们的应用。
nodejs中事件循环的几个阶段 在libuv内部有这样一个事件环机制。在node启动时会初始化事件环
┌───────────────────────┐
┌─ │ timers(计时器) │
| | 执行setTimeout以及 |
| | setInterval的回调。 |
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks |
│ | 处理网络、流、tcp的错误 |
| | callback |
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
| | node内部使用 |
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐ ┌───────────────┐
│ │ poll(轮询) │ │ incoming: │
| | 执行poll中的i/o队列 | ─────┤ connections, │
| | 检查定时器是否到时 | │ data, etc. |
│ └──────────┬────────────┘ └───────────────┘
│ ┌──────────┴────────────┐
│ │ check(检查) │
| | 存放setImmediate回调 |
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks |
│ 关闭的回调例如 |
| sockect.on('close') |
└───────────────────────┘
阶段总览
- timers:执行setTimeout()和setInterval安排的回调
- I/O callbacks: 执行几乎所有异常的close回调,由timer和setImmediate执行的回调。
- idle,prepare: 只用于内部
- poll : 获取新的I/O事件,node在该阶段会适当的阻塞
- check : setImmediate的回调被调用
- close callbacks: e.g socket.on(‘close’,…);
- 在每次运行事件循环之间,node.j检查是否有正在等待的异步i/o调用、timers等。如果没有,就清除并结束(退出程序),例如:执行一个程序,仅有一句话(var a= ‘hello’;),处理完目标代码后,不会进入evetloop,而是直接结束程序。
阶段详解
- timers,定时器阶段: 执行定时任务(setTimeOut(), setInterval())
- setTimeout 和 setImmediate 二者非常相似,但是二者区别取决于他们什么时候被调用.
- setImmediate 设计在poll阶段完成时执行,即check阶段;
- setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行;但其在timer阶段执行 其二者的调用顺序取决于当前event loop的上下文,如果他们在异步i/o callback之外调用(在i/o内调用因为下一阶段为check阶段),其执行先后顺序是不确定的,需要看loop的执行前的耗时情况
- poll阶段
poll 轮询阶段:
- 处理到期的定时器任务,然后(因为最开始阶段队列为空,一旦队列为空,就会检查是否有到期的定时器任务)
- 处理队列任务,直到队列空,或达到上限
- 如果队列为空:如果setImmediate,终止轮询阶段,进入检查阶段执行。如果没setImmediate,查看有没有定时器任务到期,有的话就到timers阶段,执行回调函数.
- check阶段
poll阶段变为空闲、等待状态时,一旦调用setImmediate(),eventloop会进入check 阶段,而不是在poll阶段等待。
- close callbacks阶段
例如:socket或句柄关闭,close事件会触发这个阶段。或者通过process.nextTick()触发