观察者模式
观察者模式通常又被称为“发布-订阅模式”:它定义了对象和对象间的一种依赖关系,只要当一个对象的状态发送改变时,所有依赖于它的对象都得到通知并自动更新,解决了主体对象与观察者之间功能的耦合。
生活中观察者模式也是非常常见的,比如订阅公众号,订阅报纸,订阅各种媒体等。当订阅的主题的改变发送改变,比如有新的消息,就通知订阅者。
class Publisher{
_state = 0;
substrbers = [];
get state () {
return this._state;
}
set state (value) {
this._state = value;
this.notify(value); // 通知订阅者
}
notify (value) {
this.substrbers.forEach(substriber => substriber.update((value)))
}
collect (subscribers) {
this.substrbers.push(subscribers)
}
}
let subId = 1;
class Substriber {
publisher = null;
id = subId++;
substriber(publisher){
this.publisher = publisher;
publisher.collect(this);
}
update (value) {
console.log(`我是${this.id}号订阅者:收到发布者信息${value}`)
}
}
let publisher = new Publisher();
let substriber1 = new Substriber();
let substriber2 = new Substriber();
// publisher.state --> 0
// substriber1 --> Substriber {publisher: null, id: 1}
// substriber2 --> Substriber {publisher: null, id: 2}
// substriber1.substriber(publisher) substriber1进行订阅publisher
// substriber2.substriber(publisher) substriber2进行订阅publisher
// publisher.state = 2 我是1号订阅者:收到发布者信息2 我是2号订阅者:收到发布者信息2
观察者的使用场景:当一个对的改变需要同时改变其他对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式 实例:各种原生事件,自定义事件。Vue源码中的
迭代器模式
迭代器模式是指提供一种方法访问一个聚合对象中各个元素,而又无需暴露该对象的内部结构。迭代器可以把迭代的过程中从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
迭代器模式分为内部迭代器和外部迭代器,内部迭代器就是在函数内部定义好迭代的规则,它完全接手整个迭代的过程,外部只需要一次初始调用。
let arr = [1, 2, 3, 4, 5];
let each = function (arr, callback) {
for (let i = 0; i < arr.length; i ++) {
callback.call(null, i, arr[i]); // 把下标和元素当作参数传递给callback参数
}
}
each(arr, function (i, value){
console.log('i', value);
})
内部迭代器再调用时非常方便,但使用回调自由度有限,录入用户想在迭代途中暂停,或者迭代两个数组需要使用each嵌套。
外部迭代器是指显示地请求迭代下一个元素,虽然这样做会增加调用地复杂度,但也会增加迭代地操作灵活性:
class Iterator {
constructor(list) {
this.list = list;
this.index = 0;
}
next () {
return {
value: this.list[this.index++],
done: this.index > this.list.length,
}
}
}
let it = new Iterator([1, 2, 3]);
it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: false}
it.next(); // {value: undefine, done: true}
迭代器模式可以为不同的数据结构提供一个统一的、简便的接口,从而支持同样的算法在不同的数据结构上进行迭代操作。
实例:
ES6语法中提出了迭代器的概念,以下对象默认实现了ES6的迭代器接口。
- Array
- Map
- Set
- String
- TyperArrey
- 函数的arguments对象
- NodeList对象 提供了调用之后能返回迭代器的接口的对象被称为可迭代对象。ES6中for...of,扩展运算符(...),数组结构,Array.from等语法迭代。
状态模式
状态模式的定义:
通常谈到封装,一般都会优先封装对象的行为。但在状态模式中是把对象的每种状态都进行封装。如果按照传统面向对象语言的角度来实现状态模式,第一,我们需要将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。第二,使用的对象没在不同的状态下具有截然不同的行为,这个对象看起来时从不同的类中实例化而来的。
状态模式在生活中很常见,例如十字路口的交通信号灯,一通电话就有拨通,通话挂断各个状态等等。
假设有一个台灯,一开始是关闭状态,按下开关后会变成弱光状态,再次按下按钮会变成强光状态,再次按下按钮变成关闭状态,如果不使用状态模式:
class Lamp {
constructor() {
this.state = 'off';
}
pressButton () {
if (this.state === 'off') {
console.log('弱光');
this.state = 'weakLight';
}else if (this.state === 'weakLight') {
console.log('强光');
this.state = 'strongLight';
}else if (this.state === 'strongLight') {
console.log('关闭');
this.state = 'off';
}
}
}
let lamp = new Lamp;
// lamp.pressButton() 弱光
// lamp.pressButton() 强光
// lamp.pressButton() 关闭
// lamp.pressButton() 弱光
这样做存在有如下问题:
- 每次新增或者修改状态名,都需要改动pressButton方法中的代码,使得pressButton成为了一个非常不稳定的方法,并且这个方法的代码量会迅速膨胀,因为状态改变后的逻辑在实际开发中不仅仅只是例子里的打印
- 状态之间的切换关心,不过是往pressButton方法里堆砌if,else语句,增加或者修改一个状态可能要改变若干个操作,这使pressButton更加难以阅读和维护
如果使用状态模式,将状态封装成类,在JS中可以这样实现:
class Lamp {
constructor() {
// 将状态抽象成对象
this.offLightState = new OfflightSatae();
this.weakLightState = new WeakLightState();
this.strongLightState = new StrongLightState();
// 当前的状态
this.state = this.offLightState;
}
pressButton () {
// 委托请求到状态对象上
this.state.trigger.call(this);
}
}
class OfflightSatae {
trigger () {
console.log('弱光');
this.state = this.weakLightState;
}
}
class WeakLightState {
trigger () {
console.log('强光');
this.state = this.strongLightState;
}
}
class StrongLightState {
trigger () {
console.log('关闭');
this.state = this.offLightState;
}
}
let lamp = new Lamp();
// lamp.pressButton() 弱光
// lamp.pressButton() 强光
// lamp.pressButton() 关闭
// lamp.pressButton() 弱光
如果状态不需要封装成类,可以用普通对象代替:
class Lamp {
constructor() {
// 当前的状态
this.state = FSM.offLightState;
}
pressButton () {
// 委托请求到状态对象上
this.state.trigger.call(this);
}
}
// 有限状态机--(符合特征)
// 1: 状态的总数是有限的
// 2: 任意时刻,指挥处在一种状态中
// 3: 在某些条件下,会从一种状态转变到另外一种状态
const FSM = {
offLightState: {
trigger () {
console.log('弱光');
this.state = FSM.weakLightState;
}
},
weakLightState: {
trigger () {
console.log('强光');
this.state = FSM.strongLightState;
}
},
strongLightState: {
trigger () {
console.log('关闭');
this.state = FSM.offLightState;
}
}
};
let lamp = new Lamp();
// lamp.pressButton() 弱光
// lamp.pressButton() 强光
// lamp.pressButton() 关闭
// lamp.pressButton() 弱光
以下这些情况中,我们应该考虑到状态模式:
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。\
实例:tab栏,货物无聊状态,音乐播放器得循环模式,游戏各种状态等。
网上的资料深浅不一,本人也是处在学习的过程中的总结,如果发现错误,欢迎留言指出~
扫码加入前端群,分享技术难题