• 定义:

一个对象维持一系列依赖于它的对象,将有关状态的任何变更信息自动通知给它们。又或者说,一个或者多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象上以便于注册所感兴趣的内容,目标状态发生改变并且观察者可能对这些变更感兴趣,就会发送一个通知消息,调用每个观察者的更新方法,当观察者不再对目标感兴趣的时候,它们可以简单地将自己从中分离。
上面两个定义根据自己的理解力各取所需。

  • 常用的场景:
  • 网页的事件绑定
  • [x] 优势:

    观察者模式本质上就是将数据的产生于数据的处理逻辑相分离开来,被观察者模式负责数据的产生,观察者则负责处理数据,即产生的数据流与处理数据的逻辑分离开来。

  • [x] 缺点:

  • 创建订阅者本身要消耗一定的时间和内存
  • 当订阅一个消息时,也许此消息并没有发生,但这个订阅者会始终存在内存中。
  • 观察者模式弱化了对象之间的联系,这本是好事情,但如果过度使用,对象与对象之间的联系也会被隐藏的很深,会导致项目的难以跟踪维护和理解。

下面简单的观察者模式栗子:

  1. //观察者列表,用于存放所有的观察者
  2. function ObserverList() {
  3. this.observerList = [];
  4. }
  5. //往观察者队列里面新增观察者
  6. ObserverList.prototype.add = function(obj) {
  7. return this.observerList.push(obj);
  8. };
  9. //统计观察者的数量
  10. ObserverList.prototype.count = function() {
  11. return this.observerList.length;
  12. };
  13. //根据下标获取对应位置的观察者
  14. ObserverList.prototype.get = function(index) {
  15. if(index > -1 && index < this.observerList.length) {
  16. return this.observerList[index ];
  17. }
  18. };
  19. //定义一个检查队列里是否有某个观察者,从指定位置开始检测,有的话返回对应观察者位置坐标,否则返回-1
  20. ObserverList.prototype.indexOf = function(obj, startIndex) {
  21. var i = startIndex;
  22. while(i < this.observerList.length) {
  23. if(this.observerList[i] === obj) {
  24. return i;
  25. }
  26. i++;
  27. }
  28. return -1;
  29. };
  30. //移除指定位置的观察者
  31. ObserverList.prototype.removeAt = function(index) {
  32. this.observerList.splice(index, 1);
  33. };
  34. //目标--继承于观察者队列,含有观察者队列的所有API
  35. function Subject() {
  36. this.observers = new ObserverList();
  37. }
  38. //增加一个目标
  39. Subject.prototype.addObserver = function(observer) {
  40. this.observers.add(observer);
  41. };
  42. //移除一个目标
  43. Subject.prototype.removeObserver = function(observer) {
  44. this.observers.removeAt(this.observers.indexOf(observer, 0));
  45. };
  46. //通知方法,通知队列里面的每一个观察者,也就是分发信息,
  47. //就是把信息注入到观察者的脑子里哦,前提是观察者有脑子(update)
  48. Subject.prototype.notify = function(context){
  49. var observerCount = this.observers.count();
  50. for(var i=0; i < observerCount; i++){
  51. this.observers.get(i).update(context);
  52. }
  53. };
  54. //观察者
  55. function Observer(name, subject) {
  56. this.name = name;//观察者名字
  57. this.subject = subject;//所要观察的目标
  58. this.subscribe(this.subject);
  59. }
  60. //这就是观察者的脑子哦
  61. Observer.prototype.update = function(context) {
  62. console.log('观察者名字:' + this.name + ', 接收到的信息:' + context);
  63. }
  64. //和目标建立联系,就是把自己放到队列里面
  65. Observer.prototype.subscribe = function(subject) {
  66. this.subject.addObserver(this);
  67. }
  68. var subject1 = new Subject();
  69. var subject2 = new Subject();
  70. var observer1 = new Observer('observer1', subject1);
  71. var observer2 = new Observer('observer2', subject1);
  72. var observer3 = new Observer('observer3', subject2);
  73. subject1.notify('发布第一个消息');
  74. subject2.notify('发布第二个消息');
  75. //观察者名字:observer1, 接收到的信息:发布第一个消息
  76. //观察者名字:observer2, 接收到的信息:发布第一个消息
  77. //观察者名字:observer3, 接收到的信息:发布第二个消息

上面代码仔细阅读,发布订阅模式的基本和观察者模式基本上一致

Publish/Subscribe发布订阅者模式

  1. var pubsub = {};
  2. (function (q) {
  3. var topics = {},//全部事件的集合
  4. subUid = -1,
  5. subscribers,
  6. len;
  7. //发布广播事件,包含特定的topic名称和需要传递的参数
  8. q.publish = function (topic, args) {
  9. if (!topics[topic]) {//如果这个事件不存在,那么终止交易
  10. return false;
  11. }
  12. subscribers = topics[topic];//将这个事件注册全局
  13. len = subscribers ? subscribers.length : 0;
  14. //遍历这个事件的的全部订阅者,将信息传递给全部订阅者
  15. while (len--) {
  16. subscribers[len].func(topic, args);
  17. }
  18. return this;
  19. };
  20. //订阅事件
  21. q.subscribe = function (topic, func) {
  22. if (!topics[topic]) {//订阅的时候,可能这个事件不存在,后面可能会有,所以定义一个事件容器
  23. topics[topic] = [];
  24. }
  25. //生成唯一键 token
  26. var token = (++subUid).toString();
  27. //将订阅事件,放进事件容器,为了方便后面定位,设置一个token方便后面查找
  28. topics[topic].push({
  29. token: token,
  30. func: func,
  31. });
  32. return token;
  33. };
  34. //取消订阅
  35. q.unsubscribe = function (token) {
  36. for (var m in topics) {
  37. if (topics[m]) {
  38. for (var i = 0, j = topics[m].length; i < j; i++) {
  39. //通过唯一键 将对应的订阅者从事件容器里移除
  40. if (topics[m][i].token === token) {
  41. topics[m].splice(i, 1);
  42. return token;
  43. }
  44. }
  45. }
  46. }
  47. return this;
  48. };
  49. })(pubsub);
  50. //测试事件1
  51. function log1(topic, data) {
  52. console.log(topic, data);
  53. }
  54. //测试事件2
  55. function log2(topic, data) {
  56. console.log("Topic is " + topic + " Data is " + data);
  57. }
  58. pubsub.subscribe("sss", log2);//订阅sss事件,触发事件是log2
  59. pubsub.subscribe("sss", log1);//订阅sss事件,触发事件是log1
  60. pubsub.subscribe("cccc", log2);//订阅cccc事件,触发事件是log2
  61. pubsub.subscribe("aaa", log1);//订阅aaa事件,触发事件是log1
  62. pubsub.publish("sss", "aaaaa1");//发布事件sss,传递信息是aaaaa1
  63. pubsub.publish("cccc", "ssssss");//发布事件cccc,传递信息是sssssss
  64. pubsub.publish("aaa", "hahahahah");//发布事件aaa,传递信息是hahahahah
  65. pubsub.publish("xxx", "没人订阅我吆");//发布事件xxx,传递的信息是“没人订阅我吆”
  66. //sss aaaaa1
  67. //Topic is sss Data is aaaaa1
  68. //Topic is cccc Data is ssssss
  69. //aaa hahahahah

上面就是一个简单的发布订阅者模式,那么和观察者的区别是什么呢:

  • 观察者模式呢就像去餐馆吃饭,你向店家点了一份饭,店家会直接送过来,你跟店家是有直接交互的。
  • 发布订阅模式就像是我们微信里面订阅公众号,发布者把文章发布到这个公众号,我们订阅公众号后就会收到发布者发布的文章,我们可以关注和取消关注这个公众号。