javascript观察者模式
介绍
观察者模式又称发布-订阅模式
,它定义对象间的一种一对多的依赖关系,当一个对象发生改变的时候,所依赖它的对象都能得到通知。例如:我们订阅了一个栏目,当栏目有新文章的时候,它会自动通知所有订阅它的人。
特点
- 发布 & 订阅
- 一对多
优点
- 低耦合,观察者和观察目标都是抽象出来,容易扩展和重用
- 触发(通讯)机制,由观察目标通知所有观察它的人
缺点
- 一个观察目标下可能存在很多的观察者,那么当观察目标需要通知所有观察者的时候会花很多时间
- 观察者和观察目标之间如果存在依赖的话,可能会发生循环调用,进入死循环导致系统崩溃
- 观察者模式没有相应的机制让观察者知道观察目标是如何发生变化,仅仅知道观察目标发生了变化
- 观察目标可能将一些无用的更新发送出去
简单例子
- 例子:A,B,C三个人都关注了某一个电台,当电台发布新内容的时候通知A,B,C三个人。
- 构思:具体的构思中,我们可以知道电台作为发布者,它有了新的内容,需要向ABC这三个订阅者推送他的最新的消息。那么我们就可以知道电台这个发布者需要包含新的更新内容以及有哪些订阅者订阅了它。
代码实现:
简单的代码实现:class Radio { constructor() { this.state = 0; this.observers = []; } setState(state) { this.state = state; this.notifyingObservers(); } addObserver(observer) { this.observers.push(observer); } notifyingObservers() { const state = this.state; this.observers.forEach(observer => { observer.update(state); }); } } class People { constructor(name) { this.name = name; } update(content) { console.log(`我是${this.name},我接受到更新了,更新内容:${content}`); } } // 创建订阅者 const peopleA = new People('小A'); const peopleB = new People('小B'); const peopleC = new People('小C'); // 添加发布者 const radio = new Radio(); // 订阅 radio.addObserver(peopleA); radio.addObserver(peopleB); radio.addObserver(peopleC); // 发布者发布 radio.setState('十月份最热歌单发布了'); radio.setState('十一月份最新原创歌单发布了');
- 解读:
- 抽象了一个发布者(
Radio
),它有更新内容(setState
)、添加订阅者(addObserver
)、以及触发所有订阅者(notifyingObservers
); - 抽象了一个订阅者(
People
),他有自己的个人信息(如:name
),以及接受到通知后所需要执行的动作(updata
); - 当我们每次更新消息的时候出发
notifyingObservers
方法,将所有的observers
的update
都触发了
当然,实际上,我们上的每一个订阅者都有这个update,当这个update不满足功能需求的时候,我们同样可以将实例出来的订阅者单独设置update; 如:
peopleA.update = function(content) {
// 新代码
}
以上就是一个简单的观察者模式的例子
场景延伸
- 网页页面事件
代码案例:
<button id="btn">点击</button> <script> $('#btn').click(function() { console.log('btn被点击'); }) </script>
- 解读:可以理解成函数订阅了
$('#btn')
的click事件,当$('#btn')
的click被我们点击触发,函数收到触发信息,并自执行。 那么这个函数就是观察者(订阅者),$('#btn')
的click事件就是观察目标(发布者)。
- Promise
代码案例:
function loadImage(url) { return new Promise(function(resolve, reject) { let image = document.createElement('img'); image.onload = function () { resolve(image); } image.onerror = function () { reject('图片加载失败'); } image.src = url; }); } const src = 'http://imgsrc.baidu.com/image/c0%3Dpixel_huitu%2C0%2C0%2C294%2C40/sign=ad13ee4af0f2b211f0238d0ea3f80054/2e2eb9389b504fc26849383ceedde71190ef6df1.jpg' const img = loadImage(src); img.then(function (img) { console.log('width', img.width); return img }).then(function (img) { console.log('height', img.height); });
- 解读:promise的resolve是then的执行者,当promise的状态发生改变后(resolve的时候状态从”未完成“变为”成功“),一一执行then下的方法,那么这些then可以说是promise的观察者,当这个promise被resolve的时候,所有的观察得到了通知。
- 关于promise内部的观察者模式可以参数
https://github.com/xieranmaya/blog/issues/3
这篇文档。
promise.then会把内部的函数添加到一个callback的数组内,等异步执行完成之后在进行一次调用该函数。每一个.then会返回一个新的promise。
- js的事件触发器(自定义事件)
代码案例:
class EventEmitter() { constructor() { this.events = {}; } // 订阅事件 on(type, listener) { if (!this.events) { this.events = Object.create(null); } if (this.events[type]) { this.events[type].push(listener) } else { this.events[type] = [listener]; } } // 触发执行 emit(type, ...args) { if (this.events[type]) { this.events[type].forEach(fn => fn.call(this, ...args)); } } // 解绑 off(type, listener) { id (this.events[type]) { this.events[type] = this.events[type].filter(fn => { return fn !== listener; }); } } } const myEmitter = new EventEmitter(); myEmitter.on('log', function() {console.log('111111')}); myEmitter.emit('log');
解读:这个就和第一个案例有点相似,我们在jq中见过这样的页面事件写法:
$('id').on('click', function() { // 事件代码 });
这个就是一种事件触发器,在nodejs里面大量采用了事件触发器的方法。详情可以去看nodejs里面的
EventEmitter
方法,就可以大体理解他的机制了。
同理,我们也可以明白react里面的生命周期等mvvm框架,里面大量采用了观察者模式。它们都是定义了一个个钩子,等状态达到的时候我就触发相对应的钩子,执行相对应的代码。
总结
总之,观察者模式在javascript中的使用是非常广泛的。其低耦合的特点方便在多人开发的复杂项目中,能提高效率,使代码的维护性大大提升。