JS设计模式之观察者模式

概述

观察者模式(Observer),又叫做发布-订阅(Publish/Subscribe)模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。主题对象状态发生改变时,会通知所有的观察者对象,使它们能自动更新自己。当不需要监听某一个特定的主题对象时,可以取消订阅,即从观察者列表中删除。为此可以确定如下的类。

  • Subject(目标)
    用于维护一系列的观察者,方便添加或删除观察者。可实现为接口
  • Observer(观察者)
    为目标状态改变时需获得通知的对象提供一个更新接口,可实现为抽象类。
  • ConcreteSubject(具体目标)
    状态发生改变时,向Observer发出通知,储存ConcreteObserver的状态。
  • ConcreteObserver(具体观察者)
    储存一个指向ConcreteSubject的引用,实现Observer的更新接口,使得自身状态与目标的状态保持一致。
实现方法

ES5

//继承的核心方法
    Object.extend = function(obj, extension) {
      for (const key in extension) {
        obj[key] = extension[key];
      }
      return obj;
    };
    Object.prototype.extend = function(object) {
      return Object.extend.apply(this, [this, object]);
    };
    //通知的接口
    function Subject() {}
    Subject.prototype.attach = function(observer) {};
    Subject.prototype.detach = function(observer) {};
    Subject.prototype.notify = function() {}
    //抽像观察者类
    function Observer() {
      this.update = function() {}
    };

    function ConcreteObserver() {
      this.content = "";
    }
    ConcreteObserver.prototype = (new Observer().extend({
      update: function(msg) {
        this.content = msg
      }
    }));
    //具体的通知者类
    function ConcreteSubject() {
      this.observers = [];
    }
    //继承后实现具体的方法,并保持链式调用
    ConcreteSubject.prototype = (new Subject()).extend({
      attach: function(observer) {
        this.observers.push(observer);
        return this;
      },
      detach: function(observer) {
        this.observers.splice(this.observers.indexOf(observer), 1);
        return this;
      },
      notify: function(msg) {
        for (let i = 0, len = this.observers.length; i < len; i++) {
          this.observers[i].update(msg);
        }
        return this;
      }
    });
    const subject = new ConcreteSubject();
    const observer1 = new ConcreteObserver();
    const observer2 = new ConcreteObserver();
    //追加观察对象
    subject.attach(observer1).attach(observer2).notify("初始化状态");
    console.log(subject);
    //取消某一对象的观察
    subject.detach(observer1).notify('更改状态');
    console.log(subject);

ES6

  class Subject{
    constructor(){}
    attach(){}
    detach(){}
    notify(){}
  }
  class Observer{
    constructor(){}
    update(){}
  }
  class ConcreteObserver extends Observer {
      constructor(){
        super();
        this.content="";
      }
      update(msg){
        this.content=msg;
      }
  }
  class ConcreteSubject extends Subject{
    constructor(){
      super();
      this.observers=[];
    }
    attach(observer){
      this.observers.push(observer);
      return this;
    }
    detach(observer){
      this.observers.splice(this.observers.indexOf(observer), 1);
      return this;
    }
    notify(msg){
      for (let i = 0, len = this.observers.length; i < len; i++) {
        this.observers[i].update(msg);
      }
      return this;
    }
  }
  const subject = new ConcreteSubject();
  const observer1=new ConcreteObserver();
  const observer2=new ConcreteObserver();
  //追加观察对象
  subject.attach(observer1).attach(observer2).notify("初始化状态");
  console.log(subject);
  //取消某一对象的观察
  subject.detach(observer1).notify('更改状态');
  console.log(subject);
应用案例

复选框选中:
(1)实现复选框选中一般情况下可采取这样的方法:document.getElementByName('checkbox')获取到checkbox节点集合,然后for循环遍历设置其checked属性就可以了。但是如果复选框选中需要实现多个复杂业务如全选,首尾选中,奇数个选中,偶数个选中等功能,就需要在每个功能中实现如获取节点集合->for循环遍历->if条件判断等流程,而且获取节点结合等DOM查询在每个功能中都耦合了,DOM结构改变了所有涉及到获取节点集合的代码都需要修改。
(2)对于使用观察者模式,即可以在全局重缓存主题类,所有需要改变属性的DOM节点都可以加入到观察者集合里,包括未来新增的DOM节点也可以加入到观察者集合里。使得需要改变属性的DOM节点得到缓存,减少了DOM查询,业务代码集中放到Subject类去实现,使得代码更清晰,易维护。

<!DOCTYPE HTML>
<html>
<title>观察者设计模式</title>

<head>
  <meta charset="utf-8">
  <style type="text/css">
  </style>
</head>

<body>
  <div><label>普通新增选项:<button id="normalAddCheckBox">添加</button></div>
  <div><label>普通全选:<input type="checkbox" id="normalAllCheck"/></label></div>
    <div><label>其他普通选中:<button id="normalOddCheck">奇数个选中</button></label></div>
  <hr/>
  <div><label>观察者新增选项:<button id="observerAddCheckBox">添加</button></div>
  <div><label>观察者全选:<input type="checkbox" id="observerAllCheck"/></div>
  <div><label>其他观察者选中:<button id="observerOddCheck">奇数个选中</button><button id="firstLastCheck">首尾选中</button></label></div>
  <hr/>
  <div>
    普通新增选项区域:
    <div id="normalContainer"></div>
  </div>
  <hr/>
  <div>
    观察者新增选项区域
    <div id="observerContainer"></div>
  </div>

  <script type="text/javascript">
    document.getElementById('normalAddCheckBox').onclick = function() {
      const checkbox = document.createElement('input');
      checkbox.type = "checkbox";
      checkbox.name = "checkbox";
      document.getElementById('normalContainer').appendChild(checkbox);
    }
    //全选
    document.getElementById('normalAllCheck').onchange = function(event) {
      const checkbox = document.getElementsByName('checkbox');
        for (const item of checkbox) {
          item.checked = event.target.checked;
        }
    }
    //奇数个选中
    document.getElementById('normalOddCheck').onclick=function(){
      const checkbox = document.getElementsByName('checkbox');
      for(let i=0,len=checkbox.length;i<len;i++){
        if(i%2!=0){
          checkbox[i].checked=true;
        }
      }
    }
  </script>
  <script type="text/javascript">
    class CheckboxSubject {
      constructor() {
        this.observers = [];
        return this;
      }
      attach(checkbox) {
        this.observers.push(checkbox);
        return this;
      }
      detach(checkbox) {
        this.observers.splice(this.observers.indexOf(observer), 1);
        return this;
      }
      //全选
      notify(checked) {
        for (let i = 0, len = this.observers.length; i < len; i++) {
          this.observers[i].update(checked);
        }
      }
      //奇数选中
      oddCheck(checked) {
        for (let i = 0, len = this.observers.length; i < len; i++) {
          if (i % 2 !== 0) {
            this.observers[i].update(checked);
          }
        }
      }
      //首尾选中
      firstLastCheck(checked){
        if(this.observers.length!==0){
          this.observers[0].update(checked);
          this.observers[this.observers.length-1].update(checked);
        }
      }
      //更多选中方式可自定义
    }
    const checkboxSubject = new CheckboxSubject();
    document.getElementById('observerAddCheckBox').onclick=function(){
      const checkbox = document.createElement('input');
      checkbox.type = "checkbox";
      checkbox.name = "checkbox";
      checkbox.update=function(value){
        this.checked=value;
      }
      document.getElementById('observerContainer').appendChild(checkbox);
      checkboxSubject.attach(checkbox);
    }
    //全选
    document.getElementById('observerAllCheck').onchange = function(event) {
      checkboxSubject.notify(event.target.checked);
    };
    //奇数个选中
    document.getElementById('observerOddCheck').onclick=function(){
      checkboxSubject.oddCheck(true);
    }
    document.getElementById('firstLastCheck').onclick=function(){
      checkboxSubject.firstLastCheck(true);
    }
  </script>
</body>

</html>

实现效果
在这里插入图片描述
从上图可以看到,观察者模式只对在观察者集合里的元素进行状态的改变。

总结

观察者模式在前端开发中可应用于数据绑定,事件委托,事件集合等如Vue中的this.$emit();this.$on()Jquery里的$('#demo').on()等都用到了观察者模式(发布/订阅模式)的思想。个人觉得,对于前端开发,设计到批量操作的业务,接耦不同的功能代码如(业务订阅某一主题,主题内根据传入的msg实现具体的业务,Ajax后发布某一主题并传如msg)
等也可以吸收观察者模式的思想,应用于开发过程中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值