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
相关推荐
caimouse15 分钟前
ReactOS 图形系统分析(17):区域填充 — EngPaint / IntEngPaint(paint.c)
c语言·开发语言·算法
北风toto22 分钟前
研发效能与后端核心技术全景指南:从CI/CD到共性组件实战
java·开发语言·ci/cd
Billy1213824 分钟前
Day12-C++20 Coroutines(上):协程原理与Promise/Awaitable机制
开发语言·c++进阶学习
01二进制代码漫游日记26 分钟前
C++基础入门速通
java·开发语言·c++
Herbert_hwt27 分钟前
第六章 Java深入理解接口、函数式接口与lambda表达式
java·开发语言·算法
岁岁种桃花儿28 分钟前
Vue核心语法第十一篇:绑定样式
前端·javascript·vue.js
橙橙笔记1 小时前
C++的学习第三部分
开发语言·c++·学习
2603_965148119 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
万少10 小时前
给 DeepSeek Harness 装个"应用商店":一条命令,595 个插件随你逛
前端·javascript·后端
小爬的老粉丝12 小时前
JavaScript 纯前端预览 WPS:先识别容器,再路由解析器
前端·javascript·wps