发布-订阅模式
- 定义对象间的一种 一对多 的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都将得到通知
- 简单实现
// 定义发布者 var salesOffices = {}; // 缓存列表, 存放订阅者的回调函数 salesOffices.clientList = []; // 定义订阅者 salesOffices.listen = function (fn) { this.clientList.push(fn); } // 发布消息 salesOffices.trigger = function () { for (var i = 0, fn; fn = this.clientList[i++];) { fn.apply(this, arguments) } } /*** 测试 ***/ // 订阅者1 salesOffices.listen(function (price, squareMeter) { console.log('价格=' + price); console.log('squareMeter= ' + squareMeter); }); // 订阅者2 salesOffices.listen(function (price, squareMeter) { console.log('价格=' + price); console.log('squareMeter= ' + squareMeter); }); // 发布消息 salesOffices.trigger(2000, 80); salesOffices.trigger(3000, 100);
- 上面的实现方式, 导致了, 每个订阅者都会收到发布者发布的消息
// 定义发布者 var salesOffices = {}; // 缓存列表, 存放订阅者的回调函数 salesOffices.clientList = {}; // 定义订阅者 (增加缓存, 增加标示 key ) salesOffices.listen = function (key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); } // 发布消息 salesOffices.trigger = function () { var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false; } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments) } }
提炼发布-订阅模式
// 核心事件 var event = { clientList: {}, listen: function (key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); }, trigger: function () { var key = Array.prototype.shift.call(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false } for (var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); } } } // 取消订阅消息 event.remove = function (key, fn) { var fns = this.clientList[key]; if (!fns) { return false; } if (!fn) { // 没有传入fn 取消key对应的所有的订阅 fns && (fns.length = 0); } else { // 传入fn 删除对应的fn for (var l = fns.length - 1; l >= 0; l--) { var _fn = fns[l]; if (_fn === fn) { fns.splice(l, 1) } } } } // 给任何对象动态增加发布-订阅功能 var installEvent = function (obj) { for (var key in event) { if (event.hasOwnProperty(key)) { obj[key] = event[key]; } } } /*** 测试 ***/ var salesOffices = {}; installEvent(salesOffices); // 订阅者1 salesOffices.listen('squareMeter80', function (price) { console.log('价格=' + price); }); // 订阅者2 salesOffices.listen('squareMeter100', function (price) { console.log('价格=' + price); }); // 发布消息 salesOffices.trigger('squareMeter80', 20000); salesOffices.trigger('squareMeter100', 30000);
- 登录案例
// 登录成功, 发布成功消息 $.ajax('http://xxx.com/login', function (data) { login.trigger('loginSuccess', data); }); // 这种写法也很好 var header = (function () { // 订阅消息 login.listen('loginSuccess', function (data) { header.setAvatar(data); }) return { setAvatar: function (data) { console.log('header 模块拿到用户信息') } } })();
以上写法会有三个问题
- 需要一个类似'中介者'的角色, 把发布者和订阅者联系起来(通过 全局的 Event 来解决)
- 以上必须先订阅, 才能发布
- 全局命名的冲突
实现类似 Event 的发布-订阅模式
- 优点1: 时间上的解耦,
- 优点2: 对象之间的解耦
- 缺点1: 创建订阅者本生要消耗内存和时间
- 缺点2: 弱化了对象之间的联系之后, 对象之间的必要联系也被埋没
var Event = (function() { var global = this, Event, _default = 'default'; Event = function () { var _listen, _trigger, _remove, _slice = Array.prototype.slice, _shift = Array.prototype.shift, _unshift = Array.prototype.unshift, namespaceCache = {}, _create, find, each = function(arr, fn) { var ret; for (var i = 0, l = arr.length; i < l; i++) { var n = arr[i]; ret = fn.call(n, i, n); } return ret; }; _listen = function (key, fn, cache) { if (!cache[key]) { cache[key] = []; } cache[key].push(fn); }; _remove = function (key, cache, fn) { if (cache[eky]) { if (fn) { for (var i = cache[key].length - 1; i >= 0; i--) { if (cache[key][i] === fn) { cache[key].splice(i, 1); } } } else { cache[key] = []; } } } _trigger = function () { var cache = _shift.call(arguments), key = _shift.call(arguments), args = arguments, _self = this, ret, stack = cache[key]; if (!stack || !stack.length) { return; } return each(stack, function () { return this.apply(_self, args); }) }; _create = function (namespace) { namespace = namespace || _default; var cache = {}, offlineStack = [], ret = { listen: function (key, fn, last) { _listen(key, fn, cache); if (offlineStack === null) { return; } if (last === 'last') { offlineStack.length && offlineStack.pop()(); } else { each(offlineStack, function () { this(); }) } offlineStack = null; }, one: function (key, fn, last) { _remove(key, fn, cache); this.listen(key, fn, last); }, remove: function (key, fn) { _remove(key, cache, fn); }, trigger: function () { var fn, args, _self = this; _unshift.call(arguments, cache); args = arguments; fn = function () { return _trigger.apply(_self, args); } if (offlineStack) { return offlineStack.push(fn); } return fn(); } }; return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret } return { create: _create, one: function (key, fn, last) { var event = this.create(); event.one(key, fn, last); }, remove: function (key, fn) { var event = this.create(); event.remove(key, fn); }, listen: function (key, fn, last) { var event = this.create(); event.listen(key, fn, last); }, trigger: function () { var event = this.create(); event.trigger.apply(this, arguments); } } }() return Event; })();
观察者(observer) 模式 和 发布/订阅模式 之间的区别
- 本质上的区别及时在调度的地方不同
- 分清楚谁是发布者, 谁是观察者, 谁是订阅者
- 观察者模式
// subject(发布) 中的目标发生变化. Observer(观察) 能接受到变化 function ObserverList() { this.observerList = []; } ObserverList.prototype.add = function (obj) { return this.observerList.push(obj); } ObserverList.prototype.get = function (index) { if (index > -1 && this.observerList.length) { return this.observerList[index]; } } ObserverList.prototype.indexOf = function (obj, startIndex) { var i = startIndex; while(i < this.observerList.length) { if (this.observerList[i] === obj) { return i; } i++; } return -1; } ObserverList.prototype.removeAt = function (index) { this.observerList.splice(index, 1); } // 发布者 function Subject() { this.observers = new ObserverList(); } Subject.prototype.addObserver = function (observer) { this.observers.add(observer); } Subject.prototype.removeObserver = function (observer) { this.observers.removeAt(this.observers.indexOf(observer, 0)); } Subject.prototype.notify = function (context) { var observerCount = this.observers.count(); for (var i = 0; i < observerCount; i++) { this.observers.get(i).update(context); } } // 观察者 function Observer() { this.update = function () { // ... } }
- 发布(Publish)/订阅(Subscribe)模式
var pubsub = {}; (function (myObject) { var topics = {}; var subUid = -1; // 发布 myObject.publish = function (topic, args) { if (!topics[topic]) { return false; } var subscribers = topics[topic], len = subscribers ? subscribers.length : 0; while (len--) { subscribers[len].func(topic, args); } return this; } // 订阅者 myObject.subscribe = function (topic, func) { if (!topics[topic]) { topics[topic] = []; } var token = (++subUid).toString(); topics[topic].push({ token: token, func: func }) } // 取消订阅 myObject.unsubscribe = function (token) { for (var m in topics) { if (topics[m]) { for (var i = 0, j = topics[m].length; i < j; i++) { if (topics[m][i].token === token) { topics[m].splice(i, 1); return token; } } } } return this; } })(pubsub)