JavaScript实现观察者模式

概念:

[wiki] 观察者模式软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。

ES5下的实现

再ES5中主要是通过Object.defineProperties方法定义对象属性的设置(set)和获取(get),并再进行设置时执行相关的处理函数,如下:

var targetObj={
  age:1
}

function observer(oldval,newval){
  console.log('name属性的值从 '+oldval+'改变为 '+newval);
}

Object.defineProperty(targetObj,'name',{
  enumerable:true,
  configurable:true,
  get:function(){
    return name;
  },
  set:function(val){
    //调用处理函数
    observer(name,val);
    name=val;
  }
});

targetObj.name="www";
targetObj.name="mmm";
console.info('targetObj:',targetObj);

结果为:

name属性的值从 改变为 www
name属性的值从 www改变为 mmm
targetObj:{age:1,name:"mmm"}

ES6的实现(使用set方法实现)

class  TargetObj{
  constructor(age,name){
    this.name=name;
    this.age=age;
  }
  set name(val){
    Observer(name,val);
    name=val;
  }
}

function Observer(oldval,newval){
  console.info('name属性的值从 '+ oldval +' 改变为 ' + newval);
}

let targetObj2 = new TargetObj(1,'www');
targetObj2.name="mmm";
console.info(targetObj2);

使用Reflect和Proxy实现

在ES6中新增的Proxy Api用处很多,结合Reflect Api可以方便的实现很多强大的逻辑,详细介绍可以参见《ECMAScript 6 入门》—— 阮一峰 中的介绍。实现代码如下:

class TargetObj {
  constructor(age, name) {
    this.name = name;
    this.age = age;
  }
}

let targetObj = new TargetObj(1, "www");

let observerProxy = new Proxy(targetObj, {
  set(target, property, value, reciever) {
    if (property === "name") {
      observer(target[property], value);
    }
    Reflect.set(target, property, value, reciever);
  }
});

function observer(oldval, newval) {
  console.info(`name属性的值从${oldval} 改变为${newval}`);
}

observerProxy.name="mmm";
console.info(targetObj);

结果为:

name属性的值从www 改变为mmm
TargetObj {name: "mmm", age: 1}

通用观察者模式

完整实现

let Observer = (function() {
  // 防止消息队列暴露而被篡改,故将消息容器作为静态私有变量保存
  var __messages = {};
  return {
    regist: function regist(type, fn) {
      //如果此消息不存在则应该创建一个该消息类型
      if (typeof __messages[type] === "undefined") {
        // 将动作推入到该消息对应的动作执行队列中
        __messages[type] = [fn];
      } else {
        // 将动作方法推入该消息对应的动作执行序列中
        __messages[type].push(fn);
      }
      return this;
    },
    fire: function fire(type, args) {
      // 如果该消息没有被注册,则返回
      if (!__messages[type]) return;
      // 定义消息信息
      var events = {
        type: type,
        args: args || {}
      };
      var i = 0;
      var len = __messages[type].length;
      // 遍历消息动作
      for (; i < len; i++) {
        // 依次执行注册的消息对应的动作序列
        __messages[type][i].call(this, events);
      }
      return this;
    },
    remove: function remove(type, fn) {
      // 如果消息动作队列存在
      if (__messages[type] instanceof Array) {
        // 从最后一个消息动作遍历
        var i = __messages[type].length - 1;
        for (; i >= 0; i--) {
          // 如果存在该动作则在消息动作序列中一处相应动作
          __messages[type][i] === fn && __messages[type].splice(i, 1);
        }
      }
    }
  };
})();
简单的使用
const observerFns = {
  test: "test",
  addUser: "addUser"
};
Observer.regist(observerFns.test, e => {
  console.log(e.type, e.args.msg);
})
  .regist(observerFns.test, e => {
    console.info(e.type, e.args.msg);
  })
  .regist(observerFns.addUser, e => {
    var type = e.type;
    var args = e.args;
    console.log(args);
    ``;
  });

Observer.fire(observerFns.test, { msg: "这是我传的参数" });
Observer.fire(observerFns.addUser, { name: "wwm" });

结果

test 这是我传的参数
test 这是我传的参数
Object {name: "wwm"}

用类实现方法的自动调用

class Student {
  result: string;
  constructor(result: string) {
    this.result = result;
    this.say = this.say.bind(this); // 解决`class`的方法单独使用时`this`指向问题
  }
  say(e) {
    console.log(this.result);
  }
  answer(question: string) {
    // 注册回答问题
    Observer.regist(question, this.say);
  }
  sleep(question: string) {
    console.log(this.result + " " + question + " 已被注销");
    Observer.remove(question, this.say);
  }
}

class Teacher {
  ask(question) {
    console.log("问题是: " + question);
    Observer.fire(question, question);
  }
}
var student1 = new Student("学生1回答问题");
var student2 = new Student("学生2回答问题");
var student3 = new Student("学生3回答问题");

var teacher = new Teacher();

student1.answer("什么是设计模式");
student1.answer("简述观察者模式");
student2.answer("什么是设计模式");
student3.answer("简述观察者模式");

student3.sleep("什么是设计模式");

teacher.ask("什么是设计模式");
teacher.ask("简述观察者模式");
在ES5中使用
var Student = function(result) {
  var that = this;
  that.result = result;
  that.say = function() {
    console.log(that.result);
  };
};
Student.prototype.answer=function(question){
  Observer.regist(question,this.say)
}
Student.prototype.sleep=function(question){
  Observer.remove(question,this.say)
}

var Teacher=function(){};
Teacher.prototype.ask=function(question){
  console.log("问题是: "+question);
  Observer.fire(question,null)
}

var student1 = new Student("学生1回答问题");
var student2 = new Student("学生2回答问题");
var student3 = new Student("学生3回答问题");

var teacher = new Teacher();

student1.answer("什么是设计模式");
student1.answer("简述观察者模式");
student2.answer("什么是设计模式");
student3.answer("简述观察者模式");

student3.sleep("什么是设计模式");

teacher.ask("什么是设计模式");
teacher.ask("简述观察者模式");

输出

学生3回答问题 什么是设计模式 已被注销
问题是: 什么是设计模式
学生1回答问题
学生2回答问题
问题是: 简述观察者模式
学生1回答问题
学生3回答问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容