node.js学习-events

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.jsimmediatetimeoutPS 命令行 node .\eventloop.jsimmediatetimeoutPS 命令行 node .\eventloop.jsimmediatetimeout*/

可以发现执行的顺序正常了,但其实它只是假象

  • setTimeout和setImmediate都处在fs的回调中,也就是poll队列后
  • node执行顺序timer(setTimeout)->poll(fs回调)->check(setImmediate)。
  • 可以看出,此时poll回调结束后->清空微任务->继续执行check(setImmediate)->输出’immediate’->
  • 清空微任务 -> 继续执行timer(setTimeout)->输出’timeout’


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部