node常用内置模块(events)

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')

二、发布订阅模式

定义对象间一对多的依赖关系

模式图解:
image.png
要素:

缓存队列,存放订阅者信息
具有增加、曹处订阅的能力
状态改变时通知所有订阅者执行监听

模拟实现:

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:
事件轮询机制图解:
image.png
队列说明:

timers:执行setTimeout与setInterval回调
pendding 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');
    })
})
// 顺序确定,按队列顺序切换执行
13 声望
1 粉丝
0 条评论
推荐阅读
rollup简易使用
参考原链接:[链接]作者:Alaso来源:稀土掘金rollup安装与使用安装 {代码...} 使用不使用配置文件配置到script中 {代码...} 参数解释-i指定要打包的文件,-i是--input的缩写。src/index.js是-i的参数,即打包入...

小江不可求阅读 579

反编译微信小程序获取小程序前端源码wxapkg
研究反编译的原因就是我自己辛苦了半个月写的小程序,忘记备份放在桌面,心急体验Win11系统 重装系统忘记备份源码,后悔莫及。 后来网上找了反编译的教程,反编译已经上线了的小程序 于是自己尝试了一下,真的可...

TANKING13阅读 10.1k评论 7

封面图
Just for fun——C#应用和Nodejs通讯
进程通信常见的进程通讯的方法有:管道(Pipe)命名管道信号消息队列其他管道是比较简单基础的技术了,所以看看它。Node IPC支持Node官方文档中Net模块写着:IPC SupportThe net module supports IPC with named ...

pigLoveRabbit3阅读 6.8k评论 2

「过程详解」async await综合题
如果你之前跟我一样一直对async await熟悉又陌生的话(熟悉是可能每天都在用,陌生是针对一些组合题又丈二和尚摸不着头脑),不妨可以边看边练,总结规律,相信会逐渐清晰并有所得。本文对每个案例都详细描述了代...

wuwhs5阅读 1.3k

封面图
cligetter|一款快速生成 Cli工具 开发模版的脚手架
近年来 cli工具 的开发,对于不断发展的前端生态来说,似乎也逐渐成为工程师们的必备技能。其实开发一个 cli工具 并不难,但对于前端的同学可能存在一点认知上的小门槛,特别是对于刚开始接触 cli 脚手架工具开发...

木木剑光3阅读 684

一个灵活的 Node.js 多功能爬虫库 —— x-crawl
x-crawl · x-crawl 是一个灵活的 Node.js 多功能爬虫库。灵活的使用方式和众多的功能可以帮助您快速、安全、稳定地爬取页面、接口以及文件。如果你也喜欢 x-crawl ,可以给 x-crawl 存储库 点个 star 支持一下,...

coderhxl2阅读 2k评论 2

封面图
从零打造你的前端开发脚手架
在实际开发过程中,我们经常都会用到脚手架来构建前端工程项目,常见的主流框架都有自己的脚手架,vue-cli、create-react-app、angular-cli。在大型公司都会有内部定制化的脚手架开发工具,使用脚手架可以大幅提...

南城FE2阅读 3.1k

封面图
13 声望
1 粉丝
宣传栏