一、Node的事件订阅发布
1.EventEmitter
Node中很多模块都能够使用EventEmitter,有了EventEmitter才能方便的进行事件的监听。下面看一下Node.js中的EventEmitter如何使用。
(1) 基本使用
EventEmitter是对事件触发和事件监听功能的封装,在node.js中的event模块中,event模块只有一个对象就是EventEmitter,下面是一个最基本的使用方法:
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('some_event', function() {
console.log('some_event 事件触发');
});
setTimeout(function() {
event.emit('some_event');
}, 1000);
上面的代码中首先实例化了一个EventEimitter对象,然后就可以进行事件的监听以及发布。通过on方法对特定的事件进行监听,通过emit方法对事件进行发布。在1s后发布一个"some_event"事件,这个时候就会自动被event对象通过on进行监听,并触发对应的回调方法。
(2)EventEmitter支持的方法
EventEmitter实例对象支持的方法列表如下:
emitter.on(name, f) //对事件name指定监听函数f
emitter.once(name, f) //与on方法类似,但是监听函数f是一次性的,使用后自动移除
emitter.listeners(name) //返回一个数组,成员是事件name所有监听函数
emitter.removeListener(name, f) //移除事件name的监听函数f
emitter.removeAllListeners(name) //移除事件name的所有监听函数
......
同时,事件的发布emit方法可以传入多个参数,第一个参数是定义的事件,后面其他参数回作为参数传递到监听器的回调函数中。
2.自定义EventEmitter
为了能够更容易理解其内部监听事件的实现的原理,所以自己封装了一个模块如下:
function EventEmitter(){
this._events = {};//初始化一个私有的属性
}
//type 绑定的事件名
// listen 事件发生后的监听
function EventEmitter(){
this._events = {};//初始化一个私有的属性
}
//type 绑定的事件名
// listen 事件发生后的监听
EventEmitter.prototype.on = EventEmitter.prototype.addListener= function(type,listener){
if(this._events[type]){
this._events[type].push(listener);
}else{
this._events[type] = [listener];
}
};
EventEmitter.prototype.once = function(type,listener){
function callOnce() {
listener.apply(this, arguments);
this.removeListener(type, callOnce);
}
this.on(type, callOnce);
};
EventEmitter.prototype.emit = function(type){
var callbacks = this._events[type];
var args = Array.prototype.slice.call(arguments,1);
var self = this;
callbacks.forEach(function (callback) {
callback.apply(self, args);
})
};
EventEmitter.prototype.removeListener=function(type,listener){
if(this._events[type]){
var listeners = this._events[type];
for(var i=0;i<listeners.length;i++){
if(listeners[i] === listener){
listeners.splice(i,1);
return;
}
}
}
};
module.exports = EventEmitter;
通过以下方式实现:
var EventEmitter = require('./EventEmitter');
var util = require('util');
function Girl(name){
this.name = name;
EventEmitter.call(this);
}
util.inherits(Girl,EventEmitter);
var girl = new Girl();
function Boy(name){
this.name = name;
this.say = function(thing){
console.log(thing);
}
}
var xiaoming = new Boy('小明');
var xiaohua = new Boy('小花');
//注册监听 事件 订阅
girl.addListener('看',xiaoming.say);
//注册监听 事件 订阅
girl.on('看',xiaohua.say);
//发射事件 发布
girl.emit('看','钻石');
girl.removeListener('看',xiaoming.say);
//girl.removeAllListeners('看');
girl.emit('看','项链');
3.NodeJs的events事件
如下一些关键点是需要注意的
(1)给监听器传入参数与 this
eventEmitter.emit() 方法允许将任意参数传给监听器函数。 当一个普通的监听器函数被 EventEmitter 调用时,标准的 this 关键词会被设置指向监听器所附加的 EventEmitter。
const myEmitter = new MyEmitter();
myEmitter.on('event', function(a, b) {
console.log(a, b, this);
// 打印:
// a b MyEmitter {
// domain: null,
// _events: { event: [Function] },
// _eventsCount: 1,
// _maxListeners: undefined }
});
myEmitter.emit('event', 'a', 'b');
也可以使用 ES6 的箭头函数作为监听器。但是这样 this 关键词就不再指向 EventEmitter 实例:
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
console.log(a, b, this);
// 打印: a b {}
});
myEmitter.emit('event', 'a', 'b');
(2)异步与同步
EventEmitter 会按照监听器注册的顺序同步地调用所有监听器。 所以需要确保事件的正确排序且避免竞争条件或逻辑错误。 监听器函数可以使用 setImmediate() 或 process.nextTick() 方法切换到异步操作模式:
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
setImmediate(() => {
console.log('这个是异步发生的');
});
});
myEmitter.emit('event', 'a', 'b');
(3)错误事件
当 EventEmitter 实例中发生错误时,会触发一个 'error' 事件。 这在 Node.js 中是特殊情况。
如果 EventEmitter 没有为 'error' 事件注册至少一个监听器,则当 'error' 事件触发时,会抛出错误、打印堆栈跟踪、且退出 Node.js 进程。
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
// 抛出错误,并使 Node.js 崩溃
为了防止 Node.js 进程崩溃,可以在使用 domain 模块。 (注意,domain 模块已被废弃。)
作为最佳实践,应该始终为 'error' 事件注册监听器。
const myEmitter = new MyEmitter();
myEmitter.on('error', (err) => {
console.error('有错误');
});
myEmitter.emit('error', new Error('whoops!'));
// 打印: 有错误
(4)emitter.removeListener
新增于: v0.1.26
eventName <any>
listener <Function>
从名为 eventName 的事件的监听器数组中移除指定的 listener。
const callback = (stream) => {
console.log('有连接!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
removeListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定
eventName 的监听器数组中,则必须多次调用 removeListener 才能移除每个实例。
注意,一旦一个事件被触发,所有绑定到它的监听器都会按顺序依次触发。 这意味着,在事件触发后、最后一个监听器完成执行前,任何 removeListener() 或 removeAllListeners() 调用都不会从 emit() 中移除它们。 随后的事件会像预期的那样发生。
const myEmitter = new MyEmitter();
const callbackA = () => {
console.log('A');
myEmitter.removeListener('event', callbackB);
};
const callbackB = () => {
console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// callbackA 移除了监听器 callbackB,但它依然会被调用。
// 触发是内部的监听器数组为 [callbackA, callbackB]
myEmitter.emit('event');
// 打印:
// A
// B
// callbackB 被移除了。
// 内部监听器数组为 [callbackA]
myEmitter.emit('event');
// 打印:
// A
因为监听器是使用内部数组进行管理的,所以调用它会改变在监听器被移除后注册的任何监听器的位置索引。 虽然这不会影响监听器的调用顺序,但意味着由 emitter.listeners() 方法返回的监听器数组副本需要被重新创建。
返回一个 EventEmitter 引用,可以链式调用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。