事件操作
zKing 2018-12-08 Node.js
摘要
Node.js 中不存在浏览器中冒泡,捕获这些行为,所以 Node.js 实现了 events 这个模块来进行事件操作。
# events 模块
- 大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)。
- 所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。
- 事件的命名通常是驼峰式的字符串。
简单写一下示例
// 引入 events 模块
var EventEmitter = require('events');
// 继承 EventEmitter 类
class CustomEvent extends EventEmitter{} //继承
// 实例化 CustomEvent 对象
const event = new CustomEvent()
// 创建事件处理程序
var connectHandle = function () {
console.log('连接成功。');
event.emit('data_received', "在 connect 事件中来触发 data_received 事件");
};
// 绑定 connect 事件处理程序
event.on('connect', connectHandle);
// 使用匿名函数绑定 data_received 事件
// 注意,这里要在“触发 connect 事件”的语句之前先执行
event.on('data_received', function (param) {
console.log(param + ',' + '数据接收成功。');
});
// 触发 connect 事件
event.emit('connect');
console.log("程序执行完毕。");
# 事件循环
- Node.js 使用事件驱动模型,当
web server
接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。 - 当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。
- 这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)
- 在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数
以下是官网提供的 Node.js 事件循环的解释图。按照队列的方式,会依此按优先顺序进行事件循环。详情点击 这里
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
写一下详细代码
event-loop.js
const fs = require('fs')
fs.readFile('./package.json',function(){
console.log("callback 触发")
})
new Promise((resolve,reject)=>{
resolve()
}).then(function(){
console.log("Promise 触发")
})
setTimeout(function(){
console.log("setTimeout 触发")
},0)
setImmediate(function(){
console.log("setImmediate 触发")
})
process.nextTick(function(){
console.log("proccess.nextTick 触发")
})
结果
proccess.nextTick 触发
Promise 触发
setTimeout 触发
setImmediate 触发
callback 触发
从这个结果来进行推测
- 是不是代码块执行顺序问题?调换了几次位置,但是结果打印都一样
- 如果不是代码块执行顺序问题?那可不可以结果来作为事件循环的队列顺序?
再将 event-loop.js
写得复杂点
const fs = require('fs')
let a = 1;
let b = 2;
let res = a + b;
console.log("事件执行:" + res)
setImmediate(function () {
console.log("setImmediate 触发")
fs.readFile('./package.json', function () {
console.log("callback 三次触发")
})
})
setTimeout(function () {
console.log("setTimeout 触发")
process.nextTick(function () {
console.log("proccess.nextTick 二次触发")
})
fs.readFile('./package.json', function () {
console.log("callback 二次触发")
})
new Promise((resolve, reject) => {
resolve()
}).then(function () {
console.log("Promise 二次触发")
})
}, 0)
fs.readFile('./package.json', function (err, data) {
data = JSON.parse(data.toString())
console.log("callback 触发")
console.log(data.license)
setTimeout(function(){
console.log('-----------')
},0)
process.nextTick(function () {
console.log("proccess.nextTick 三次触发")
})
})
new Promise((resolve, reject) => {
resolve()
}).then(function () {
console.log("Promise 触发")
})
process.nextTick(function () {
console.log("proccess.nextTick 触发")
})
结果
事件执行:3
proccess.nextTick 触发
Promise 触发
setTimeout 触发
proccess.nextTick 二次触发
Promise 二次触发
setImmediate 触发
callback 触发
ISC
proccess.nextTick 三次触发
-----------
callback 二次触发
callback 三次触发
根据以上示例,所以如果只是想简单地知道结果的话。那就是
proccess.nextTick
和Promise
应该是作为程序切入的阶段setTimeout
和setImmediate
应该是 timer 阶段callback
应该是事件循环的解释图中除了 timer 的剩余阶段。- 在执行完
fs.readFile
后进入pending callbacks
,即等待回调阶段 - 之后根据
function (err, data)
继续执行idle, prepare
来进行系统内部调用 - 然后进入
poll
阶段,填充数据,并进行操作 - 再进入
chcek
阶段,即轮询阶段,判断是否还有定时器等,再来执行一次。 - 最后关闭回调函数,即进入
close callbacks
阶段
- 在执行完