本文介绍了观察者模式的定义,作用,和基本实现的要点,希望对你有帮助。
场景
用户点了网页中的按钮,接下要做三件事情(假设三件事没有依赖关系)。
初始方案1:伪代码:
document.getElementById('btn').addEventListener('click', { doSomething1() doSomething2() doSomething3() }) 复制代码
上面的三个函数表示三件不同的事情。
好了,就从这里出发,我们看看这段代码,然后提一个需求:额外再补充做一件事。
解决:定义一个函数doSomething4,然后添加到click的回调中,如下:
document.getElementById('btn').addEventListener('click', { doSomething1() doSomething2() doSomething3() doSomething4() // 这是新添加的 }) 复制代码
看起来,需求可以很方便的满足,也没有什么问题。但是,我们直接修改了 原来的初始代码 。
大家会觉得:添加新功能要改原来的代码,不是很正常吗,这些代码是不都是我们自己写的吗,改改有什么关系?
有关系,两个理由:
- 理论上:违反了软件设计的开闭原则
- 实操中:原来的代码是加密的,或者看起来就非常复杂,或者你在远程指导其他人来完成这个需求,或者不是我们自己写的..... 这些情况都让 直接修改原来代码 变得很困难。
以上是背景,下面引出观察者模式的写法。来!
改进方案:伪代码
document.getElementById('btn').addEventListener('click', doSomething1) document.getElementById('btn').addEventListener('click', doSomething2) document.getElementById('btn').addEventListener('click', doSomething3) 复制代码
需求:额外再补充做一件事。不需要改动原来的代码哈,只需要添加一句:
document.getElementById('btn').addEventListener('click', doSomething1) document.getElementById('btn').addEventListener('click', doSomething2) document.getElementById('btn').addEventListener('click', doSomething3) document.getElementById('btn').addEventListener('click', doSomething4) // 额外再补充做一件事 复制代码
现在有没有觉得,改进的方案比初始方案好呢?其实,改进的方案中就使用了观察者模式,这是DOM2级事件机制提供给我们的便利。
观察者模式的定义
观察者模式是23种设计模式中的一种。 设计模式就是解决一类编码问题的最优解,就是套路。这个概念与具体的编程语言关系不大,但是由于各个编程语言的特性不同,同一个设计模式在不同的语言中的实现成本也不同。
观察者模式的英文是Observer,下面来看它的两个经典定义:
《js设计模式》中对Observer的定义:一个对象(称为subject)维持一系列依赖于它(观察者)的对象,将有关状态的任何变更自动通知给它们。
《设计模式:可复用面向对象软件的基础》中对Observer的定义:一个或多个观察者对目标的状态感兴趣,他们通过将自己依附在目标对象上以便注册所感兴趣的内容。目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标感兴趣时,他们可以简单地将自己从中分离。
注意一下,在各个不同的资料(语言背景)中可能看到的定义表述是不同的,不过,他们有共同点:
- 描述一对多的关系。多个观察者对一个目标感兴趣
- 目标变化之后,会主动通知观察者(他们分别做出各自的行为)
- 观察者是可以任意添加和移除的(对目标不再感兴趣就移除)
我们再来看看前面写的代码,检查是否符合上面的标准:
document.getElementById('bnt').addEventListener('click', doSomething1) document.getElementById('bnt').addEventListener('click', doSomething2) document.getElementById('bnt').addEventListener('click', doSomething3) document.getElementById('bnt').addEventListener('click', doSomething4) // 额外再补充做一件事 复制代码
说明如下:
- 描述一对多的关系。四个观察者(doSomething1,2,3, 4)对一个目标(按钮是否点击)感兴趣
- 目标变化之后,会主动通知观察者(按钮点击之后,4个动作各自执行)
- 观察者是可以任意添加和删除(addEventListener来添加观察者,removeEventListener来删除观察者)
写一个观察者模式
下面,用一段简单的代码来实现观察者模式:
// 大漂亮是女神,有很多的爱慕者 const beauty = { lover: [], // 容器,保存全部的观察者 notify() { // 通知全部观察者 this.lover.forEach(fn => fn()); }, addLover(fn) { // 添加观察者 this.lover.push(fn); }, removeLover(fn) { // 移除观察者 const idx = this.lover.findIndex(item => item === fn); if (idx != -1) { this.lover.splice(idx, 1); } }, }; const lover1 = () => { console.log('1号爱慕者'); }; const lover2 = () => { console.log('2号爱慕者'); }; const lover3 = () => { console.log('3号爱慕者'); }; beauty.addLover(lover1); // 添加观察者 beauty.addLover(lover2); // 添加观察者 beauty.addLover(lover3); // 添加观察者 beauty.notify(); // 1,2,3号 beauty.removeLover(lover1); // 移除观察者 beauty.notify(); // 2,3号 复制代码
上面代码的核心要点:
- 一个容器来保存观察者
- 一个动作通知全体观察者
- 两个动作(添加,移除)来操作观察者
给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
简单应用
看个观察者模式的应用。
下边的代码实现了一个效果:修改了对象的属性值,在视图上有两个地方变化了。
一个值变了,两个地方跟着做出改变。是不是一个使用观察者的好场景呢?以下是实现代码
<body> <div id="id1"></div> <div id="id2"></div> </body> <script> function observer(key, _value) { let obj = {} let listers = [] function notify() { listers.forEach(fn => fn(_value)) } function addLister(fn) { listers.push(fn) } Object.defineProperty(obj, key, { get() { return _value }, set(newVal) { if (newVal !== _value) { _value = newVal notify() } } }) return { obj, addLister } } const { obj, addLister } = observer('age', 10) addLister(val => { document.getElementById('id1').innerHTML = 'listener1 ' + val }) addLister(val => { document.getElementById('id2').innerHTML = 'listener2 ' + 2 * val }) obj.age = 10 </script> 复制代码
小结
本文介绍了观察者模式用来解决的问题、定义,基本实现,最后完成了一个小小的响应式 功能, 如果对你理解观察者模式有帮助,欢迎你转发,关注,小额打赏。
下一篇,我们来介绍 发布订阅者模式并比较它和观察者模式的区别。