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.js
 immediate
 timeout
 PS 命令行 node .\eventloop.js
 immediate
 timeout
 PS 命令行 node .\eventloop.js
 immediate
 timeout
 */

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

  • setTimeout和setImmediate都处在fs的回调中,也就是poll队列后
  • node执行顺序timer(setTimeout)->poll(fs回调)->check(setImmediate)。
  • 可以看出,此时poll回调结束后->清空微任务->继续执行check(setImmediate)->输出’immediate’->
  • 清空微任务 -> 继续执行timer(setTimeout)->输出’timeout’
  • 24
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值