node常用内置模块(events)
一、事件模块events
通过EventEmitter类实现事件统一管理
常用API:
on:添加事件监听
emit:触发事件
once:添加只触发一次的事件监听
off:移除特定的监听
代码展示:
const EventEmitter = require('events');
const ev = new EventEmitter();
ev.once('start', (...args) => { // 只执行一次
console.log('start once', args);
});
ev.on('start', function() {
console.log('tart on');
console.log('this',this);
})
ev.emit('start','message data');
// off
let startcb = ()=>{
console.log('jiang')
}
ev.on('startcb', startcb);
ev.off('startcb',startcb);
ev.emit('startcb'); // 不会触发
const fs = require('fs');
// 许多内置模块都继承了EventEmitter类,可直接使用该类的方法
const crt = fs.createReadStream();
crt.on('data')
二、发布订阅模式
定义对象间一对多的依赖关系
缓存队列,存放订阅者信息
具有增加、曹处订阅的能力
状态改变时通知所有订阅者执行监听
模拟实现:
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(callback => {
callback.call(this, ...args);
})
}
}
}
let ps = new PubSub();
ps.subscribe('event1',(...args)=>{
console.log('event1 run end',args);
})
ps.publish('event1','event1 data')
EventEmitter模拟实现:
function MyEvent(){
// 用户缓存订阅者信息
this._events = Object.create(null); // 创建一个空对象,不带任何原型
}
MyEvent.prototype.on = function(type,callback){
this._events[type] = this._events[type] || [];
this._events[type].push(callback);
}
MyEvent.prototype.emit = function(type,...args){
if(this._events[type] && this._events[type].length){
this._events[type].forEach(callback=>{
callback.call(this,...args);
})
}else{
console.log('没有订阅者');
}
}
MyEvent.prototype.off = function(type,callback){
// 判断当前 type 事件监听是否存在,如果存在,则取消指定的事件监听
if(this._events && this._events[type]){
this._events[type] = this._events[type].filter(cb=>{
return cb !== callback && cb.link !==callback;
})
}
}
MyEvent.prototype.once = function(type,callback){
let cb = (...args)=>{
callback.call(this,...args);
this.off(type,cb);
}
cb.link = callback;
this.on(type,cb);
}
let ev = new MyEvent();
let cb = function(...args){
console.log(args);
}
ev.once('event1',cb);
ev.off('event1',cb); // 取消监听
ev.emit('event1',1,2);
ev.emit('event1',1,2);
三、事件轮询eventloop
基于浏览器的事件轮询eventloop:
从上至下执行所有的同步代码
执行过程中,将遇到的宏任务与微任务依次添加到相应的任务列队中
同步代码执行完成过后,执行满足条件的微任务回调
微任务队列执行完毕之后执行所有满足需求的宏任务回调
执行事件轮询操作
注意:每次执行完一个宏任务之后就会立即检查微任务队列
练习:
setTimeout(() => {
console.log('s1');
Promise.resolve().then(() => {
console.log('p2');
})
Promise.resolve().then(() => {
console.log('p3');
})
})
Promise.resolve().then(() => {
console.log('p1');
setTimeout(() => {
console.log('s2');
})
setTimeout(() => {
console.log('s3');
})
})
// p1 s1 p2 p3 s2 s3
nodejs下的事件轮询eventloop:
事件轮询机制图解:
队列说明:
timers:执行setTimeout与setInterval回调
pedding callbacks:执行系统操作的回调,例如:tcp udp
idle,prepare:只在系统内部进行使用
poll:执行与I/O相关的回调
check:执行setImmediate中的回调
close callbacks:执行close事件的回调
nodejs完整事件轮询:
执行同步代码,将不同的任务添加至相应的队列
所有同步代码执行完成之后就会执行满足添加的微任务
所有微任务代码执行后会执行timer队列中满足条件的宏任务
timer中的所有宏任务执行完成后(10版本之前是却换任务队列之前)就会依次切换队列
注意:每次执行完一个宏任务之前都会先'清空'微任务代码,nextTick优先级高于promise
练习1:
setTimeout(()=>{
console.log('s1');
})
Promise.resolve().then(()=>{
console.log('p1');
})
console.log('start');
process.nextTick(()=>{
console.log('nextTick');
});
setImmediate(()=>{
console.log('setImmediate');
})
console.log('end');
// start end nextTick p1 s1 setImmediate
练习2:
setTimeout(()=>{
console.log('s1');
Promise.resolve().then(()=>{
console.log('p1');
})
process.nextTick(()=>{
console.log('t1');
})
})
Promise.resolve().then(()=>{
console.log('p2');
})
console.log('start');
setTimeout(()=>{
console.log('s2');
Promise.resolve().then(()=>{
console.log('p3');
})
process.nextTick(()=>{
console.log('t2');
})
});
console.log('end');
//start end p2 s1 t1 p1 s2 t2 p3
nodejs与浏览器事件轮询的区别
任务队列数不同
每次执行完一个宏任务都会去清空微任务
nodejs中process.nextTick优先于promise.then
nodejs事件轮询的常见问题:
// setTimeout(()=>{
// console.log('timeout');
// }) // 虽然默认是0,但是cpu的时间片不确定,所以可能在setImmediate之后入队执行
// setImmediate(()=>{
// console.log('immediate');
// })
// 执行顺序不确定
const fs = require('fs');
fs.readFile('./m1.js',()=>{
setTimeout(()=>{
console.log('timeout');
},0);
setImmediate(()=>{
console.log('immediate');
})
})
// 顺序确定,按队列顺序切换执行