博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从观察者模式到手写EventEmitter源码
阅读量:6295 次
发布时间:2019-06-22

本文共 4104 字,大约阅读时间需要 13 分钟。

观察者模式

观察者模式(observer)广泛的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。设计这种模式背后的主要原因是促进形成低耦合,在这种模式中不是简单的对象调用对象,而是一个对象“订阅”另一个对象的某个活动,当对象的活动状态发生了改变,就去通知订阅者,而订阅者也称为观察者。

报纸订阅

生活中就像是去报社订报纸,你喜欢读什么报就去报社去交钱订阅,当发布了新报纸的时候,报社会向所有订阅了报纸的每一个人发送一份,订阅者就可以接收到。

我们可以利用这个例子来使用javascript来模拟一下。假设有一个发布者Jack,它每天出版报纸杂志,订阅者Tom将被通知任何时候发生的新闻。

Jack要有一个subscribers属性,它是一个数组类型,订阅的行为将会按顺序存放在这个数组中,而通知意味着调用订阅者对象的某个方法。因此,当用户Tom订阅信息的时候,该订阅者要向Jack的subscribe()提供他的一个方法。当然也可以退订,我不想再看报纸了,就调用unsubscribe()取消订阅。

一个简单的观察者模式应有以下成员:

  • subscribes 一个数组
  • subscribe() 将订阅添加到数组里
  • unsubscribe() 把订阅从数组中移除
  • publish() 迭代数组,调用订阅时的方法

这个模式中还需要一个type参数,用于区分订阅的类型,如有的人订阅的是娱乐新闻,有的人订阅的是体育杂志,使用此属性来标记。

我们使用简单的代码来实现它:

var Jack = {    subscribers: {        'any': []    },    //添加订阅    subscribe: function (type = 'any', fn) {        if (!this.subscribers[type]) {            this.subscribers[type] = [];        }        this.subscribers[type].push(fn); //将订阅方法保存在数组里    },    //退订    unsubscribe: function (type = 'any', fn) {        this.subscribers[type] =            this.subscribers[type].filter(function (item) {                 return item !== fn;            }); //将退订的方法从数组中移除    },    //发布订阅    publish: function (type = 'any', ...args) {        this.subscribers[type].forEach(function (item) {             item(...args);    //根据不同的类型调用相应的方法        });    }};

以上就是一个最简单的观察者模式的实现,可以看到代码非常的简单,核心原理就是将订阅的方法按分类存在一个数组中,当发布时取出执行即可。

下面使用Tom来订报:

var Tom = {    readNews: function (info) {        console.log(info);    }};//Tom订阅Jack的报纸Jack.subscribe('娱乐', Tom.readNews);Jack.subscribe('体育', Tom.readNews);//Tom 退订娱乐新闻:Jack.unsubscribe('娱乐', Tom.readNews);//发布新报纸:Jack.publish('娱乐', 'S.H.E演唱会惊喜登台')Jack.publish('体育', '欧国联-意大利0-1客负葡萄牙');

运行结果:

欧国联-意大利0-1客负葡萄牙

观察者模式的实际应用

可以看到观察者模式将两个对象的关系变得十分松散,当不需要订阅关系的时候删掉订阅的语句即可。那么在实际应用中有哪些地方使用了这个模式呢?

events模块

node.js的events是一个使用率很高的模块,其它原生node.js模块都是基于它来完成的,比如流、HTTP等,我们可以手写一版events的核心代码,看看观察者模式的实际应用。

events模块的功能就是一个事件绑定,所有继承自它的实例都具备事件处理的能力。首先它是一个类,我们写出它的基本结构:

function EventEmitter() {    //私有属性,保存订阅方法    this._events = {};}//默认最大监听数EventEmitter.defaultMaxListeners = 10;module.exports = EventEmitter;

下面我们一个个将events的核心方法实现。

on方法

首先是on方法,该方法用于订阅事件,在旧版本的node.js中是addListener方法,它们是同一个函数:

EventEmitter.prototype.on =    EventEmitter.prototype.addListener = function (type, listener, flag) {        //保证存在实例属性        if (!this._events) this._events = Object.create(null);        if (this._events[type]) {            if (flag) {//从头部插入                this._events[type].unshift(listener);            } else {                this._events[type].push(listener);            }        } else {            this._events[type] = [listener];        }        //绑定事件,触发newListener        if (type !== 'newListener') {            this.emit('newListener', type);        }    };

因为有其它子类需要继承自EventEmitter,因此要判断子类是否存在_event属性,这样做是为了保证子类必须存在此实例属性。而flag标记是一个订阅方法的插入标识,如果为'true'就视为插入在数组的头部。可以看到,这就是观察者模式的订阅方法实现。

emit方法

EventEmitter.prototype.emit = function (type, ...args) {    if (this._events[type]) {        this._events[type].forEach(fn => fn.call(this, ...args));    }};

emit方法就是将订阅方法取出执行,使用call方法来修正this的指向,使其指向子类的实例。

once方法

EventEmitter.prototype.once = function (type, listener) {    let _this = this;    //中间函数,在调用完之后立即删除订阅    function only() {        listener();        _this.removeListener(type, only);    }    //origin保存原回调的引用,用于remove时的判断    only.origin = listener;    this.on(type, only);};

once方法非常有趣,它的功能是将事件订阅“一次”,当这个事件触发过就不会再次触发了。其原理是将订阅的方法再包裹一层函数,在执行后将此函数移除即可。

off方法

EventEmitter.prototype.off =    EventEmitter.prototype.removeListener = function (type, listener) {        if (this._events[type]) {        //过滤掉退订的方法,从数组中移除            this._events[type] =                this._events[type].filter(fn => {                    return fn !== listener && fn.origin !== listener                });        }    };

off方法即为退订,原理同观察者模式一样,将订阅方法从数组中移除即可。

prependListener方法

EventEmitter.prototype.prependListener = function (type, listener) {    this.on(type, listener, true);};

此方法不必多说了,调用on方法将标记传为true(插入订阅方法在头部)即可。

以上,就将EventEmitter类的核心方法实现了。

小结

通过创建“可观察的”对象,当发生一个感兴趣的事件时可将该事件通告给所有观察者,从而形成松散的耦合。

部分实例参考《JavaScript模式》作者:Stoyan Stefanov

转载地址:http://brkta.baihongyu.com/

你可能感兴趣的文章
这是我的第1个C#程序(向控制台输出一句话)
查看>>
html
查看>>
Xqk.Data数据框架开发指南:丰富的、灵活的查询方法(第三部分:SqlField)
查看>>
Java基本语法
查看>>
MapReduce对交易日志进行排序的Demo(MR的二次排序)
查看>>
online-compiler 在线编译器
查看>>
9. Palindrome Number - Easy
查看>>
使用vs2017编译live555
查看>>
洛谷——P1347 排序
查看>>
uboot2009 nandflash移植
查看>>
gulp-usemin 插件使用
查看>>
int数据类型的最大数
查看>>
OI养老专题02:约瑟夫问题求幸存者
查看>>
Python多线程
查看>>
写作环境搭建(git+github+markdown+jekyll)
查看>>
Codeforces Round #443 (Div. 2) C. Short Program
查看>>
flash builder4的序列号(阻止adobe更新)
查看>>
Android横竖屏切换的生命周期
查看>>
python之生成随机密码
查看>>
jekens介绍及服务搭建
查看>>