JavaScript 观察者设计模式

观察者模式:观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。而js中最常见的观察者模式就是事件触发机制。

ES5/ES6实现观察者模式(自定义事件) - 简书

先搭架子

  1. 要有一个对象,存储着它自己的触发函数。而且这个对象的触发函数可能有很多种,比如一个onclick可能触发多个事件,那么handler的属性应该是一个数组,每个数组的值都是一个函数。
js 复制代码
handler={
  type1:[func1,func2...],
  type2:[func3,func4...],
  ...
}
  1. 现在这个对象的主体部分已经思考好了,现在就是要它'动起来',给它添加各种动作。
    一个事件可能有哪些动作呢?
  • add:添加事件某种类型的函数,
  • remove: 移除某种类型的函数,
  • fire:触发某种类型的函数,
  • once:触发某种类型的函数,然后移除掉这个函数
  1. 现在,自定义事件的架子已经搭建好了
js 复制代码
eventOb={
  //函数储存
  handler:{
    type1:[func1,func2...],
    type2:[func2,func4...],
    ...
  },

  //主要事件
  add:function(){},
  remove:function(){},
  fire:function(){},
  once:function(){},
}

add

添加一个事件监听,首先传入参数应该是 事件类型type,和触发函数 func,传入的时候检测有没有这个函数,有了就不重复添加。

js 复制代码
add:function (type,func) {
  //检测type是否存在
  if(eventOb.handleFunc[type]){
    //检测事件是否存在,不存在则添加
    if(eventOb.handleFunc[type].indexOf(func)===-1){
      eventOb.handleFunc[type].push(func);
    }
  }
  else{
    eventOb.handleFunc[type]=[func];
  }
},

remove

remove有一个潜在的需求,就是如果你的事件不存在,它应该会报错。而这里不会报错,index在func不存在的时候是-1;这时候要报错。

js 复制代码
remove:function (type,func) {
  try{
    let target = eventOb.handleFunc[type];
    let index = target.indexOf(func);
    if(index===-1) throw error;
    target.splice(index,1);
  }catch (e){
      console.error('别老想搞什么飞机,删除我有的东西!');
  }
},

fire

触发一个点击事件肯定是要触发它全部的函数,这里也是一样,所以只需要传入type,然后事件可能不存在,像上面一样处理。

js 复制代码
fire:function (type,func) {
  try{
    let target = eventOb.handleFunc[type];
    let count = target.length;
    for (var i = 0; i < count; i++) {
      //加()使立即执行
      target[i]();
    }    
  }
  catch (e){
    console.error('别老想搞什么飞机,触发我有的东西!');
  }
},

once

fire,然后remove?

但会有问题,我只想触发并且删除某个事件怎么办,fire一下就全触发了呀。

所以fire的问题就显现出来了。我们还是要给它一个func,但是可选。

js 复制代码
fire:function (type,func) {
  try{
    let target = eventOb.handleFunc[type];
    if(arguments.length===1) {
      //不传func则全部触发
      let count = target.length;
      for (var i = 0; i < count; i++) {
          target[i]();
      }
    }else{
      //传func则触发func
      let index=target.indexOf(func);
      if(index===-1)throw error;
      func();
    }
    //need some code
  }catch (e){
    console.error('别老想搞什么飞机,触发我有的东西!');
    //need some code
  }
},

完整代码

js 复制代码
class eventObs {
  constructor(){
    this.handleFunc={}
  }

  add(type,func){
    if(this.handleFunc[type]){
        if(this.handleFunc[type].indexOf(func)===-1){
            this.handleFunc[type].push(func);
        }
    }else{
        this.handleFunc[type]=[func];
    }
  };

  fire(type,func){
    try{
      if(arguments.length===1) {
          let target = this.handleFunc[type];
          let count = target.length;
          for (var i = 0; i < count; i++) {
              target[i]();
          }
      }else{
          let target = this.handleFunc[type];
          let index=target.indexOf(func);
          if(index===-1)throw error;
          func();
      }
      return true;
    }catch (e){
        console.error('别老想搞什么飞机,触发我有的东西!');
        return false;
    }
  };

  remove(type,func){
      try{
          let target = this.handleFunc[type];
          let index=target.indexOf(func);
          if(index===-1)throw error;
          target.splice(index,1);
      }catch (e){
          console.error('别老想搞什么飞机,删除我有的东西!');
      }

  };

  once(type,func) {

    this.fire(type, func)
    ? this.remove(type, func)
    : null;
  }
}

使用ES6的Reflect来实现一个观察者模式

[!NOTE]
函数(观察者)自动观察数据对象(观察的目标),一旦对象发生变化,函数就会自动执行

js 复制代码
    // 观察者设计模式
    const queuedObservers = new Set();
    
    const observe = fn => queuedObservers.add(fn);
    const observable = obj => new Proxy(obj, {set});
    
    function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queuedObservers.forEach(observer => observer());
      return result;
    }
    
    
    // test
    const person = observable({
      name: '张三',
      age: 20
    });
    
    function print() {
      console.log(`${person.name}, ${person.age}`)
    }
    
    observe(print);
    person.name = '李四';
    // 输出
    // 李四, 20
相关推荐
就爱六点起12 分钟前
C/C++ 中的类型转换方式
c语言·开发语言·c++
我明天再来学Web渗透13 分钟前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
猫猫的小茶馆15 分钟前
【C语言】指针常量和常量指针
linux·c语言·开发语言·嵌入式软件
WilliamLuo33 分钟前
MP4结构初识-第一篇
前端·javascript·音视频开发
DanielYQ43 分钟前
LCR 001 两数相除
开发语言·python·算法
yngsqq1 小时前
037集——JoinEntities连接多段线polyline和圆弧arc(CAD—C#二次开发入门)
开发语言·c#·swift
Zԅ(¯ㅂ¯ԅ)1 小时前
C#桌面应用制作计算器进阶版01
开发语言·c#
过期的H2O21 小时前
【H2O2|全栈】JS进阶知识(七)ES6(3)
开发语言·javascript·es6
前端Hardy1 小时前
HTML&CSS:数据卡片可以这样设计
前端·javascript·css·3d·html
一路冰雨1 小时前
Qt打开文件对话框选择文件之后弹出两次
开发语言·qt