nodejs基于事件驱动
事件发布,事件订阅
- 大多数 Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)。同步函数,异步触发,通过回调函数处理异步。
例如,net.Server 会在每次有新连接时触发事件,fs.ReadStream 会在文件被打开时触发事件,stream会在数据可读时触发事件。
所有能触发事件的对象都是 EventEmitter 类的实例。 这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串。
当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。
- 这是node.js文档的一个例子:
我的代码下载index.js文件里,通过node index执行
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('触发事件');
});
myEmitter.emit('event'); //这个就会触发myEmitter.on里的回调函数
- 实际上这个例子不太好,我再处理一下:
放在以前,我们处理"异步"都是这样做的(需要的话会传一些参数啥的):
let fn = () => {
console.log("数据结果的处理");
}
setTimeout(() => {
console.log("得到数据");
fn()
}, 2000);
在纯的js里,虽然可以达到预期目标,但是层层嵌套的函数实在是太让人头疼,为了减少这种麻烦,node.js的events的事件监听就能很好的解决。
- EventEmitter,发布-订阅模式:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter
let fn = () => {
console.log("数据结果的处理");
}
//为了好看设置的定时*********
setTimeout(() => {
console.log("1s");
}, 1000);
setTimeout(() => {
console.log("1.5s");
}, 1500);
setTimeout(() => {
console.log("2s");
}, 2000);
//************************
setTimeout(() => {
console.log("2.5s 得到数据");
myEmitter.emit("dataMake")
}, 2500);
myEmitter.on("dataMake",fn)
这样,异步触发回调函数将会变得更加好归类、结构化。
用于对象
- 让对象继承事件的EventEmitter的原型:
const EventEmitter = require("events").EventEmitter
function FnObj(params) {
this.name = params;
}
FnObj.prototype.__proto__=EventEmitter.prototype
const obj = new FnObj("Lucy");
obj.on("ly",function() {
console.log(this.name);
//注意箭头函数没有this哦
})
setTimeout(() => {
obj.emit("ly")
}, 2000);
这就是node.js的事件,发布-订阅模式
events使用进阶
异步执行的处理函数
虽然对函数的处理变得更加结构有序,但是异步执行总会发生一些数据竞争的情况,谁也不知道什么时候数据能被真正的准备好供异步函数使用。
或许有人会说我的处理函数在数据得到返回的函数里显示调用呢?然后现实开发中并不一定完全按照你的想法来执行,很多网站的数据并不能仅通过一个表单获取,也不一定保证两个数据的先后结果,在两个数据集合得到时间不一致或不能保证一致的情况下,异步执行就是个问题。
所以,处理好函数执行顺序则是至关重要的:
那么在此之前,你需要了解一下node.js的事件循环,我准备了相当详细的介绍:https://blog.csdn.net/qq_32842925/article/details/83446329
- 第一步,搞清楚到底传了什么参数进去
传参数和this到你的监听器:
eventEmitter.emit() 方法可以传任意数量的参数到监听器函数。 当监听器函数被调用时,this 关键词会被指向监听器所绑定的 EventEmitter 实例
不要用箭头函数
不要用箭头函数
不要用箭头函数
重要的事情说三遍,箭头函数没有this,它根本指不到你的实例
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
myEmitter.on('event', function (a, b) {
console.log(a, b);
console.log(this);
console.log(this === myEmitter);
});
myEmitter.emit('event', '参数a', '参数b');
贴心的我又简单优化了node文档的打印结果,更加易读
但是如果有些人就是倔强,我就要写箭头函数,我牛逼:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
myEmitter.on('event', (a, b)=> {
console.log(a, b);
console.log(this);
console.log(this === myEmitter);
});
myEmitter.emit('event', '参数a', '参数b');
对比上一次的结果:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
myEmitter.on('event', (a, b,othis)=> {
console.log(a, b);
console.log(othis);
console.log(othis === myEmitter);
});
myEmitter.emit('event', '参数a', '参数b', myEmitter);
非要用箭头函数,但是这样不很麻烦吗?
同步还是异步,无所谓
- 加上参数传递,一切都不是障碍了:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
let data = {}
myEmitter.on('event', function (data) {
setImmediate(() => {
//假如这里有个数据结构化、图形化的处理函数
console.log('异步进行结果处理');
console.log(data.name, data.age, data.height,data.sex);
});
});
myEmitter.emit('event', data); //虽然我立马就准备触发数据处理函数
process.nextTick(() => {
console.log("同步的代码去获取到了数据")
data.name = "小芳";
data.age = "18"
} )
process.nextTick(() => {
console.log("同步的代码去获取到了数据")
data.height = "1米8"
})
Promise.resolve("女").then(res => {
console.log("异步的then函数也去拿了一下数据")
data.sex = res
})
即便我先于获取数据的函数调用了数据处理的函数,但是结合同步异步函数,数据处理得到了最后的保障:
也有人可能会问,你这数据暴露在全局这样做有意义吗?
回答:这里是为了调用方便,你完全可以将数据请求封装起来,让他们在局部执行,并将事件触发也放在局部作用域里,参数传递只是事件发布函数的基本功能,合理使用效果更佳!
监听事件的行为
- 监听事件的创建:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
myEmitter.on('newListener',function() {
console.log('有事件创建出来');
})
myEmitter.on('event', function () {
console.log('事件处理函数调用');
});
// myEmitter.emit('event')
//打印:有事件创建出来
如果我重新开启事件触发,那么结果是这样
- 事件解绑:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
function fn() {
console.log('事件处理函数调用');
}
myEmitter.on('newListener',function() {
console.log('有事件创建出来');
})
myEmitter.on('event', fn);
myEmitter.emit('event')
myEmitter.off('event', fn);
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')
事件解绑是将回调函数与监听的事件分离,如果想上面一样直接把回调函数写在事件绑定函数里是无法解绑,下面是错误的示范:
- 监听事件的解绑:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
function fn() {
console.log('事件处理函数调用');
}
myEmitter.on('newListener',function() {
console.log('有事件创建出来');
})
myEmitter.on('removeListener', function () {
console.log('有事件被解绑');
})
myEmitter.on('event', fn);
myEmitter.emit('event')
myEmitter.off('event', fn);
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')
- 或者只让事件执行一次:
const EventEmitter = require("events").EventEmitter
const myEmitter = new EventEmitter();
function fn() {
console.log('事件处理函数调用');
}
myEmitter.on('newListener',function() {
console.log('有事件被创建1');
})
myEmitter.on('newListener',function() {
console.log('有事件被创建2');
})
myEmitter.on('removeListener', function () {
console.log('有事件被解绑');
})
myEmitter.once('event', fn);
console.log('1111111111111111111111');
myEmitter.emit('event')
console.log('2222222222222222222222');
myEmitter.emit('event')
myEmitter.emit('event')
myEmitter.emit('event')
两个事件监听器弄清楚流程:
事件新增监听器1被创建,但是当时没有监听事件创建的监听器;
事件新增监听器2被创建,触发了事件创建监听器1,打印:事件被创建1;
事件移除监听器被创建,触发事件创建监听器1,2,打印:事件被创建1,事件被创建2;
事件’event’创建一次性事件,触发事件创建监听器1,2,打印:事件被创建1,事件被创建2;
log打印:11111111111111111;
emit触发事件event,触发解绑事件监听器,处理事件队列里的event事件,
打印:有事件被解绑,事件处理函数被调用;
log打印:22222222222222222;
由于事件event被解绑,无法被调用,后面没有打印结果。。。
这里需要注意的就是,nodejs里的事件队列的事件是在监听创建触发后再执行事件的触发