• JS设计模式——观察者模式(通俗易懂)


    Observer模式的概念

    Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。

    Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。

    Observer模式的角色

    Subject(被观察者)

    被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。

    ConcreteSubject

    被观察者的具体实现。包含一些基本的属性状态及其他操作。

    Observer(观察者)

    接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。

    ConcreteObserver

    观察者的具体实现:得到通知后将完成一些具体的业务逻辑处理。

    观察者模式( 又叫发布者-订阅者模式 )应该是最常用的模式之一. 在很多语言里都得到大量应用. 包括我们平时接触的dom事件. 也是js和dom之间实现的一种观察者模式.

    div.onclick = function click (){
      alert ("click")
    }

    只要订阅了div的click事件. 当点击div的时候, function click就会被触发.

    那么到底什么是观察者模式呢. 先看看生活中的观察者模式。

    好莱坞有句名言. “不要给我打电话, 我会给你打电话”. 这句话就解释了一个观察者模式的来龙去脉。 其中“我”是发布者, “你”是订阅者。

    再举个例子,我来公司面试的时候,完事之后每个面试官都会对我说:“请留下你的联系方式, 有消息我们会通知你”。

    在这里“我”是订阅者, 面试官是发布者。所以我不用每天或者每小时都去询问面试结果, 通讯的主动权掌握在了面试官手上。而我只需要提供一个联系方式。

    观察者模式可以很好的实现2个模块之间的解耦。 假如我正在一个团队里开发一个html5游戏. 当游戏开始的时候,需要加载一些图片素材。

    加载好这些图片之后开始才执行游戏逻辑. 假设这是一个需要多人合作的项目. 我完成了Gamer和Map模块, 而我的同事A写了一个图片加载器loadImage.

    loadImage的代码如下

    loadImage(imgAry, function () {
      Map.init();
      Gamer.init();
    })

    当图片加载好之后, 再渲染地图, 执行游戏逻辑. 嗯, 这个程序运行良好. 突然有一天, 我想起应该给游戏加上声音功能. 我应该让图片加载器添上一行代码.

    loadImage(imgAry, function () {
      Map.init();
      Gamer.init();
      Sount.init();
    })

    可是写这个模块的同事A去了外地旅游. 于是我打电话给他, 喂. 你的loadImage函数在哪, 我能不能改一下, 改了之后有没有副作用.

    如你所想, 各种不淡定的事发生了. 如果当初我们能这样写呢:

    loadImage.listen("ready", function () {
      Map.init();
    })
    loadImage.listen("ready", function () {
      Gamer.init();
    })
    loadImage.listen("ready", function () {
      Sount.init();
    })

     loadImage完成之后, 它根本不关心将来会发生什么, 因为它的工作已经完成了. 接下来它只要发布一个信号

    loadImage.trigger( "ready" );

    下面是对观察者模式的实现

    /**
     * 发布订阅模式(观察者模式)
     * handles: 事件处理函数集合
     * on: 订阅事件
     * emit: 发布事件
     * off: 删除事件
    **/
    
    class PubSub {
      constructor() {
        this.handles = {};
      }
    
      // 订阅事件
      on(eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
          this.handles[eventType] = [];
        }
        if (typeof handle == 'function') {
          this.handles[eventType].push(handle);
        } else {
          throw new Error('缺少回调函数');
        }
        return this;
      }
    
      // 发布事件
      emit(eventType, ...args) {
        if (this.handles.hasOwnProperty(eventType)) {
          this.handles[eventType].forEach((item, key, arr) => {
            item.apply(null, args);
          })
        } else {
          throw new Error(`"${eventType}"事件未注册`);
        }
        return this;
      }
    
      // 删除事件
      off(eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
          throw new Error(`"${eventType}"事件未注册`);
        } else if (typeof handle != 'function') {
          throw new Error('缺少回调函数');
        } else {
          this.handles[eventType].forEach((item, key, arr) => {
            if (item == handle) {
              arr.splice(key, 1);
            }
          })
        }
        return this; // 实现链式操作
      }
    }
    
    // 下面做一些骚操作
    let callback = function () {
      console.log('you are so nice');
    }
    
    let pubsub = new PubSub();
    pubsub.on('completed', (...args) => {
      console.log(args.join(' '));
    }).on('completed', callback);
    
    pubsub.emit('completed', 'what', 'a', 'fucking day');
    pubsub.off('completed', callback);
    pubsub.emit('completed', 'fucking', 'again');

    输出值:

    what a fucking day

    you are so nice

    fucking again

     

  • 相关阅读:
    舍不得花钱的心理分析
    DLL编程的导入导出,__declspec(dllimport),__declspec(dllexport)
    浅谈C/C++内存泄漏及其检测工具
    C++多线程编程简单实例
    linux镜像源设置
    Linux基础教程 linux无密码ssh登录设置
    兄弟连教育分享:用CSS实现鼠标悬停提示的方法
    PHP基础教程 PHP的页面缓冲处理机制
    Linux基础教程 linux下cat 命令使用详解
    PHP基础教程 php 网络上关于设计模式一些总结
  • 原文地址:https://www.cnblogs.com/minigrasshopper/p/9134196.html
Copyright © 2020-2023  润新知