观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。
一个目标者对象 Subject,拥有方法:添加 / 删除 / 通知 Observer;
多个观察者对象 Observer,拥有方法:接收 Subject 状态变更通知并处理;
目标对象 Subject 状态变更时,通知所有 Observer。
代码实现:
class Subject {
constructor() {
this.observers = []; //观察者列表
}
//添加
add(observer) {
this.observers[this.observers.length] = observer;
}
//删除
remove(observer) {
let idx = this.observers.findIndex(item = > item === observer);
idx > -1 && this.observers.splice(idx, 1);
}
//通知
notify() {
for (let observer of this.observers) {
observer.update();
}
}
}
//观察者类
class Observer {
constructor(name) {
this.name = name;
}
// 目标对象更新时触发的回调
update() {
console.log(`i am ${this.name}`);
}
}
//实例化目标者
let subject = new Subject();
//实例化两个观察者
let obs1 = new Observer('前端');
let obs2 = new Observer('后端');
//向目标者添加观察者
subject.add(obs1);
subject.add(obs1);
// 目标者通知更新
subject.notify();
优势:目标者与观察者,功能耦合度降低,专注自身功能逻辑;观察者被动接收更新,时间上解耦,实时接收目标者更新状态。
缺点:观察者模式虽然实现了对象间依赖关系的低耦合,但却不能对事件通知进行细分管控,如 “筛选通知”,“指定主题事件通知”
发布订阅模式:基于一个事件(主题)通道,希望接收通知的对象 Subscriber 通过自定义事件订阅主题,被激活事件的对象 Publisher 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
发布订阅模式与观察者模式的不同,“第三者” (事件中心)出现。目标对象并不直接通知观察者,而是通过事件中心来派发通知。
代码实现:
//事件中心
let pubSub = {
list: {},
subscribe: function (key, fn) { //订阅
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key][this.list[key].length] = fn;
},
publish: function (key,...arg){ //发布
for (let fn of this.list[key]) {
fn.call(this,...arg);
}
},
unSubscribe: function(key, fn) { //取消订阅
let fnList = this.list[key];
if (fnList) return false;
if (!fn) { //不传入指定取消的订阅方法,则清空所有key下的订阅
fnList && (fnList.length = 0);
} else {
fnList.forEach((item, index) => {
if(item === fn){
fnList.splice(index, 1);
}
})
}
}
}
// 订阅
pubSub.subscribe('onwork', time = > {
console.log(`上班了:${time}`);
})
pubSub.subscribe('pullwater', time = > {
console.log(`划水了:${time}`);
})
pubSub.subscribe('offwork', time = > {
console.log(`下班了:${time}`);
})
//发布
pubSub.publish('pullwater', '12:00:00');
pubSub.publish('offwork', '18:00:00');
//取消订阅
pubSub.unSubscribe('onwork');
很多人都会因为上述的区别(认为发布订阅模式多一个事件中心处理订阅发布)从而得出这是两种模式,其实观察者模式就是发布订阅模式,只是上述的第二段代码比第一段代码实现这种模式实现的更好一些,弥补了第一段代码的缺点。模式可以由多种多样的代码来实现,其核心还都是定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。
1、 DOM 事件监听就是 “发布订阅模式” 的典型应用:
//事件
let loginBtn = document.getElementById('#loginBtn');
//监听回调函数(指定事件)
function notifyClick(){
console.log('我被点击了)
}
//添加事件监听
loginBtn.addEventListener('click', notifyClick);
//触发点击,事件中心派发指定事件
loginBtn.click();
//取消事件监听
loginBtn.removeEventlistener('click', notifyClick);
2、jQuery 的 on 和 trigger,$.callback();
// 自定义事件,自定义回调
var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function (info) {
console.log('fn1', info)
})
callbacks.add(function (info) {
console.log('fn2', info)
})
callbacks.add(function (info) {
console.log('fn3', info)
})
//添加完后统一触发。
callbacks.fire('gogogo')
3、Vue 的双向数据绑定;
Observer:观察者,这里的主要工作是递归地监听对象上的所有属性,在属性值改变的时候,触发相应的watcher。
Watcher:订阅者,当监听的数据值修改时,执行响应的回调函数(Vue里面的更新模板内容)。
Dep:订阅管理器,连接Observer和Watcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher
/* 实现数据监听器(数据劫持)*/
function Observer(obj, key, value) {
var dep = new Dep();
if (Object.prototype.toString.call(value) == '[object Object]') {
Object.keys(value).forEach(function(key) {
new Observer(value, key, value[key])
})
};
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {
dep.addSub(Dep.target);
};
return value;
},
set: function(newVal) {
value = newVal;
dep.notify();
}
})
}
// 订阅器
function Dep() {
this.subs = [];
this.addSub = function (watcher) {
this.subs.push(watcher);
}
this.notify = function() {
this.subs.forEach(function(watcher) {
watcher.update();
});
}
}
// 观察者
function Watcher(fn) {
this.update = function() {
Dep.target = this;
fn();
Dep.target = null;
}
this.update();
}
// 连接器
输入的值为:{{text}}
4、Vue 的父子组件通信 $on/$emit
5、promise
var src = 'http://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
console.log('success 1')
}, function () {
console.log('failed 1')
})
// then就是一个观察者,等待前一个处理完成