javascript.basics icon indicating copy to clipboard operation
javascript.basics copied to clipboard

JS设计模式观察者模式(发布/订阅模式)

Open Kelichao opened this issue 9 years ago • 4 comments

发布订阅模式

  • 可以简单的理解为普通市民订阅报纸(有多个需求不同报纸的人)。
  • 报社分发报纸(有多家不同类型的报社)

下面模拟一下场景

image

// 方法就是人员,报社列一张订阅表,对应的报社在分发的时候执行对应列表的方法即可
// 人员1订阅了报纸1,报纸2
people1.subscribe("publisher1").subscribe("publisher2");

// 人员2订阅了报纸3,报纸4
people2.subscribe("publisher3").subscribed("publisher4");

// 报社1 分发报纸,触发人员1的订阅,于是人员1收到报社1的报纸
publisher1.deliver(data);

// 报社2 分发报纸,触发人员1的订阅,于是人员1收到报社2的报纸
publisher2.deliver(data);

// 报社3 分发报纸,触发人员2的订阅,于是人员2收到报社3的报纸
publisher3.deliver(data);//增加

// 报社4 分发报纸,触发人员2的订阅,于是人员2收到报社4的报纸
publisher4.deliver(data);//增加

模拟一个简单的订阅模式

	/*	
		Publisher 代表出版社构造器
		subscribers 代表投递报纸的列表
		deliver  分发
		subscribe 订阅报纸的接口
		publisher1  出版社实例对象
		observer1 实例化的读者
	*/

	// 出版社(权力制高点)
	function Publisher() {

		// 订阅了本出版社的读者列表
		this.subscribers = [];
	}

	// 添加一个迭代方法,遍历所有投递列表 
	Publisher.prototype.deliver = function(data) {
		// 遍历当前出版社对象所有的订阅过的方法
		this.subscribers.forEach(function(fn) {
			//data用于传参数给内部方法
			fn.fire(data);
		});
		return this;
	}


	// 观察者
	function Observe(callback) {
		this.fire = callback;
	}

	// 给予订阅者阅读的能力
	Observe.prototype.subscribe = function(publisher) {

		var that = this;
		// some如果有一个返回值为true则为true
		var alreadyExists = publisher.subscribers.some(function(el){

			// 这里的el指的是函数对象
			return el.fire === that.fire;
		});

		// 如果存在这个函数对象则不进行添加
		if (!alreadyExists) {
			publisher.subscribers.push(this);
		}

		//订阅列表
		console.log(publisher.subscribers);
		return this;
	};

	// 给予观察者退订的能力
	Observe.prototype.unsubscribe = function(publisher) {

		var that = this;

		// 过滤,将返回值为true的重组成数组
		publisher.subscribers = publisher.subscribers.filter(function(el) {

			// 这里的el.fire指的是观察者传入的callback
			// that.fire就是当前对象对callback的引用
			return !(el.fire === that.fire);
		})
		console.log(publisher.subscribers)
		return this;
	};

	var publisher1 = new Publisher();

	// 实例化的读者,这个需要改进
	var observer1 = new Observe(function() {
			console.log(111)
		});

	// 读者订阅了一家报纸,在报社可以查询到该读者已经在订阅者列表了,
	// publisher1.subscribers->[observer1]
	observer1.subscribe(publisher1);
                                                                              
	// 分发报纸,执行所有内在方法
	publisher1.deliver(123);// 输出123

	// 退订
	observer1.unsubscribe(publisher1);

订阅发布模式的实践

源码地址

源码链接

生成函数列队

demo链接

kit.Control中的回调列队方式

callDemo.subscribe({
	// 普通函数回调列队,参数取argue数组
	// 默认会先调用callback的函数队列
	// 如果想优先调用queue中的队列可以单独写一个的queue函数
	callback: ["bbb","ccc"],
	// 特殊队列函数,参数自定义
	queue: {
		"aaa": [666]
	}
});

Promise包装器

demo链接

Kelichao avatar Nov 10 '16 08:11 Kelichao

这里引用下JS设计模式书本内容

image image image image image image

Kelichao avatar Nov 11 '16 08:11 Kelichao

@Kelichao 感觉以下代码有部分问题:

// 方法就是人员,报社列一张订阅表,对应的报社在分发的时候执行对应列表的方法即可
// 人员1订阅了报纸1,报纸2
people1.subscribe("publisher1").subscribe("publisher2");

// 人员2订阅了报纸3,报纸4
people2.subscribe("publisher3").asubscribed("publisher4");

// 报社1 分发报纸1,2(触发事件)
publisher1.deliver(data);

// 报社2 分发报纸3,4(触发事件)
publisher2.deliver(data);

publisher2.deliver(data);并没有触发3,4事件吧? 订阅publisher2的只有people1.

还有people2.subscribe("publisher3").asubscribed("publisher4");有个错误是asubscribed("publisher4");多写了一个a...

建议改成如下:

// 方法就是人员,报社列一张订阅表,对应的报社在分发的时候执行对应列表的方法即可
// 人员1订阅了报纸1,报纸2
people1.subscribe("publisher1").subscribe("publisher2");

// 人员2订阅了报纸3,报纸4
people2.subscribe("publisher3").subscribed("publisher4");

// 报社1 分发报纸,触发人员1的订阅,于是人员1收到报社1的报纸
publisher1.deliver(data);

// 报社2 分发报纸,触发人员1的订阅,于是人员1收到报社2的报纸
publisher2.deliver(data);

// 报社3 分发报纸,触发人员2的订阅,于是人员2收到报社3的报纸
publisher3.deliver(data);//增加

// 报社4 分发报纸,触发人员2的订阅,于是人员2收到报社4的报纸
publisher4.deliver(data);//增加

若有错请指正~


谢谢指正

obetame avatar Mar 14 '17 02:03 obetame

通俗易懂,不过场景有哪些啊,vue和react的双向绑定是defineProperty和subscribe结合起来,实现的吗?

chendayang avatar Jul 23 '18 07:07 chendayang

// 实例化的读者,这个需要改进
var observer1 = new Observe(function() {
  console.log(111)
});

这个部分是不是改成

// 实例化的读者,这个需要改进
var observer1 = new Observe(function(msg) {
  console.log(msg)
});

才能正常运行?

ghost avatar Aug 05 '19 03:08 ghost