观察者模式,是JavaScript设计模式之一。固然也不只仅限于JavaScript这门语言,网上对该模式的介绍已经是多如牛毛,并且讲得各有特点各有心得。即使如此,笔者仍精心准备了这篇博客,指望用最简单的方式来介绍下该模式。前端
首先来看下维基百科对 观察者模式 的解释:git
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理全部相依于它的观察者对象,而且在它自己的状态改变时主动发出通知。这一般透过呼叫各观察者所提供的方法来实现。此种模式一般被用来实时事件处理系统。
其实笔者更倾向于它的另外一个名字发布/订阅模式(Publish/Subscribe),由于更能表达出该模式的核心思路,那就是:发布和订阅两个过程。是否是还感受模棱两可?不用担忧,下面就用我们身边发生的事情来作个形象化的解释:
你们都有订阅网站邮件的经历吧?若是你没有的话,emmmmm....那就继续往下看吧哈哈!!
假如我今天想订阅xxx公司的邮件,那么这里就涉及到两个对象:我
和xxx公司
,
从行为上来看就是我订阅
了xxx公司邮件,xxx公司会发送
邮件给个人邮箱。但某天我不想再收到xxx公司的邮件了,那么我能够取消订阅
,这样xxx公司就不会再发邮件到个人邮箱。设计模式
说到这里,是否是就有点眉头了呢?好,咱们继续往下说,
经过刚刚的形象化解释,咱们能够罗列下观察者模式的一些核心的东西:数组
对象:我(订阅者)
, xxx公司(发布者)
, 能够直接对应 发布/订阅模式(Publish/Subscribe)
行为:订阅
、发送
和取消订阅
说不如作,下面开始用代码来更直观的描绘下观察者模式吧。
首先咱们定义一个发布者 (至关于xxx公司)函数
let publisher = { }
那么一块儿来按照订阅邮件的过程想象下,发布者具备那些属性或者方法?网站
publisher
中一定会有一个方法提供给咱们实现订阅
。publisher
中一定会有一个方法提供给咱们实现发送
或者说说发布
。publisher
中一定会有一个方法提供给咱们实现取消订阅
。publisher
中一定会有一个“注册表”
来存储订阅的对象,也就是说咱们的 邮箱地址。说到这里一切都了然了,下面仍是讲想象到的东西用代码表达出来吧this
let publisher = { registration: {}, subscribe: function (type, fn) {}, unSubscribe: function (type, fnName) {}, publish: function (type, message) {} }
简单解释下,spa
registration
就是上面提到的注册表
,至于为何把它设计成一个对象是由于考虑到xxx公司可能有更多类型的邮件,好比 游戏,金融,投资理财等等,因此就把它设计成对象以key-value的形式存储订阅者, 好比:{'game':[],'monetary':[]}
该形式subscribe
则是publisher
提供给咱们的对其进行订阅的方法,参数是type
和 fn
。type就是邮件的类型,fn就是咱们提供给publisher用于通知个人渠道 (邮箱)。在JavaScript中更多的是回调函数。unSubscribe
是publisher
提供给咱们的对其进行取消订阅的方法,参数是type
和 fnName
。type就很少说了,fnName则是咱们提供给publisher
用于取消订阅的标志,好比说邮箱,或者是回调函数的名字等等。publish
说到比较重要的方法,这就是publisher
向全部订阅者发布消息的方法。下面开始一步一步得实现三个方法,registration
保持不变:设计
首先是
subscribe
subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }
这里的思路是将 Callback Function 存储到registration
对于类型的数组中,以待publish
调用。code
而后是
unSubscribe
unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }
思路是首先经过 type 肯定数组对象,而后经过方法对象的名字进行判断,最后直接剔除操做。** 这里有个小知识点提一下:函数对象的name属性就是该函数名 **
最后是
publish
publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } }
思路是经过 type 找到指定数组,而后对数组中的回调函数进行依次调用,达到发布
的目的。
写到这里,发布者Publisher
已经完成。那么下面开始写订阅者Subscriber
,如上面所说其实订阅者就是一个 回调函数,例如:
let subscriber = function (param) { //do something }
因此下面将整个代码展现并演示下效果:
let publisher = { registration: {}, subscribe: function (type, fn) { if (Object.keys(this.registration).indexOf(type) >= 0) { this.registration[type].push(fn); } else { this.registration[type] = []; this.registration[type].push(fn); } }, unSubscribe: function (type, fnName) { if (Object.keys(this.registration).indexOf(type) >= 0) { let index = -1; this.registration[type].forEach(function (func, idx) { if (func.name === fnName) { index = idx; } }) index > -1 ? this.registration[type].splice(index, 1) : null } }, publish: function (type, message) { if (Object.keys(this.registration).indexOf(type) >= 0) { for (let fn of this.registration[type]) { fn(message) } } } } let subscriberA = function (message) { console.log(`A收到通知:${message}`) }; let subscriberB = function (message) { console.log(`B收到通知:${message}`) }; let subscriberC = function (message) { console.log(`C收到通知:${message}`) }; publisher.subscribe('game', subscriberA); publisher.subscribe('game', subscriberB); publisher.subscribe('game', subscriberC); publisher.publish('game', '恭喜RNG得到LOL 2018季中赛冠军!')
运行看下结果:
结果如想象中同样。
那再试一下取消订阅
,在 publish 以前加一段
publisher.unSubscribe('game', subscriberB.name)
再运行看下结果:
咱们已经看到 订阅者B 在取消订阅后就没再收到任何消息。
其实观察者模式能作的东西还有不少,好比事件的监听、状态发生变化时的广播等等。已经有过接触的朋友均可能意识到这个模式特别灵活,在两个角色之间正常通讯的同时也尽量得实现了解耦,给开发带来极大的便利。其中有名的 Knockout 的核心之一就是观察者模式,因此说观察者模式在前端开发中起到了举足轻重的做用。
源码 在这,有兴趣的朋友能够看下
好了,写到这里本篇博客就结束了。有问题的朋友能够在下方讨论;若是文章有不足或者错误的地方,烦请你们多多指正。Thanks !!!