events与EventEmitter
- node.js是基于事件驱动的异步操作架构,内置events模块
- events模块提供了EventEmitter类
- node.js中很多内置核心模块集成EventEmitter(fs,net,http…)
EventEmitter常见API
- on:添加当事件被触发时调用的回调函数
- emit:触发事件,按照注册的序同步调用每个事件监听器
- once:添加当时间在注册之后首次被触发时调用的回调函数
- off:移除特定的监听器
const EventEmitter = require('events');
const ev = new EventEmitter()
// on
ev.on('事件1', () => {
console.log('事件1执行了---1')
})
ev.on('事件1', () => {
console.log('事件1执行了---2')
})
// emit
ev.emit('事件1')
ev.emit('事件1')
/*
* 事件1执行了---1
* 事件1执行了---2
* 事件1执行了---1
* 事件1执行了---2
* */
// once
ev.once('event1', () => {
console.log('event1---------1')
})
// once
ev.once('event1', () => {
console.log('event1---------2')
})
ev.emit('event1')
ev.emit('event1')
/*
* event1---------1
* event1---------2
* */
function eventCb(...args) {
console.log('eventCb执行了', args);
console.log(this);
}
//off
ev.on('event2', eventCb);
ev.emit('event2', 1, 2, 3)
ev.off('event2', eventCb)
ev.emit('event2', 1, 2, 3)
/*
eventCb执行了 [ 1, 2, 3 ]
* */
小细节注意_events属性下lookMyLength1和lookMyLength2的值
ev.on('lookMyLength1', function() {
console.log(this);
})
ev.on('lookMyLength2', function() {
console.log(this);
})
ev.on('lookMyLength2', function() {
console.log(1);
})
ev.emit('lookMyLength2')
/*
EventEmitter {
_events: [Object: null prototype] {
lookMyLength1: [Function (anonymous)],
lookMyLength2: [ [Function (anonymous)], [Function (anonymous)] ]
},
_eventsCount: 2,
_maxListeners: undefined,
[Symbol(kCapture)]: false
}
1
* */
发布订阅模式
定义对象之间一对多的依赖关系
发布订阅要素
- 缓存队列,存放订阅者信息
- 具有增加、删除订阅的能力
- 状态改变时通知所有订阅者执行监听
class PubSub {
constructor() {
this._events = {};
}
// 注册
subscribe(event, callback) {
if (this._events[event]) {
this._events[event].push(callback)
} else {
this._events[event] = [callback]
}
}
// 发布
publish(event, ...args) {
const items = this._events[event];
if (items && items.length) {
items.forEach(function (callback) {
callback.apply(this, args);
})
}
}
}
const ps = new PubSub()
ps.subscribe('events1', (...args) => {
console.log(1, args)
})
ps.subscribe('events1', (...args) => {
console.log(2, args)
})
ps.publish('events1', 1, 2)
EventEmitter模拟
function MyEvent() {
// 准备一个数据结构缓存订阅者信息
this._events = Object.create(null);
}
MyEvent.prototype.on = function (type, callback) {
// 判断当前次的事件是否已经存在,然后再决定如何做缓存
if (this._events[type]) {
this._events[type].push(callback)
} else {
this._events[type] = [callback]
}
}
MyEvent.prototype.emit = function (type, ...args) {
if (this._events && this._events[type].length) {
this._events[type].forEach((callback) => {
callback.call(this, ...args);
})
}
}
MyEvent.prototype.off = function (type, callback) {
if (this._events && this._events[type].length) {
this._events[type] = this._events[type].filter(cb => {
return cb !== callback &&
cb !== callback.link
})
}
}
MyEvent.prototype.once = function (type, callback) {
const fh = function (...args) {
callback.apply(this, args);
this.off(type, fh);
}
callback.link = fh;
this.on(type, fh);
}
const ev = new MyEvent()
function cb (...args) {
console.log('running...', args);
}
ev.once('events1', cb)
// ev.off('events1', cb)
ev.emit('events1', 1, 2)
ev.emit('events1', 1, 2)
nodejs下的事件环
nodejs的六个队列
- timer:执行setTimeout和setInterval回调
- pending callbacks: 执行系统操作的回调,例如tcp udp
- idle,prepare:只在系统内部进行使用
- poll:执行与I/O相关的回调
- close callbacks:执行close事件的回调
nodejs完整事件环
- 执行同步代码,将不同的任务添加至相应的队列
- 所有同步代码执行后会去执行满足条件微任务
- 所有微任务代码执行后会执行timer队列中满足的宏任务
- timer中的所有宏任务执行完成后就会依次切换队列
- 注意:在完成队列切换之前会先清空微任务代码
Node与浏览器事件环不同
- 任务队列数不同
- 浏览器中职有两个任务队列
- Nodejs中有6个事件队列
- Nodejs微任务执行时机不同
- 二者都会在同步代码执行完毕后执行微任务
- 浏览器平台下每当一个宏任务执行完毕后就清空微任务
- Nodejs平台在事件队列切换时回去清空微任务
- 微任务优先级不同
- 浏览器事件环中,微任务存放于事件队列,先进先出
- Nodejs中process.nextTick先于promise.then
Nodejs事件环常见问题
第一种
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
/**
PS 命令行 node .\eventloop.js
immediate
timeout
PS 命令行 node .\eventloop.js
timeout
immediate
PS 命令行 node .\eventloop.js
immediate
timeout
*/
可以发现执行的顺序是不确定的,有时候是timeout先执行,有时是immediate先执行。
在使用到seTimeout方法执行的时候,偶尔会遇到不可控的延时,这就导致了一个问题
- 有时候不可控因素使延时 0 变成了超过 0ms 等待的值,那么setImmediate就比setTimeout先加载到事件队列中,就先于setTimeout执行了
- 有时候准确的控制在 0ms 延时,setTimeout就被扔到timer队列中,setImmediate扔到check队列,按照node事件循环执行于是setTimeout先于setImmediate执行
第二种情况
const fs = require('fs');
fs.readFile('./test.txt', () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
})
/**
PS 命令行 node .\eventloop.js
immediate
timeout
PS 命令行 node .\eventloop.js
immediate
timeout
PS 命令行 node .\eventloop.js
immediate
timeout
*/
可以发现执行的顺序正常了,但其实它只是假象
- setTimeout和setImmediate都处在fs的回调中,也就是poll队列后
- node执行顺序timer(setTimeout)->poll(fs回调)->check(setImmediate)。
- 可以看出,此时poll回调结束后->清空微任务->继续执行check(setImmediate)->输出’immediate’->
- 清空微任务 -> 继续执行timer(setTimeout)->输出’timeout’