JS-观察者模式

如果说,你只能掌握一种设计模式,那必须毫不犹豫的选择观察者模式,也叫发布订阅模式。

es6中promise的实现、vue中双向绑定原理的实现、Node.js中的 EventEmitter事件监听器等等都应用到了观察者模式,可见其应用之广泛

那接下来具体看一下观察者模式到底是什么

介绍

一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

优点

对象之间解耦,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变

缺点

1.创建订阅者本身会消耗一定时间和内存。当订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中

2.多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护。虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。

所以要根据实际业务场景合理使用。

实现

来个通俗一点的解释吧。小明最近在找工作,好不容易约到一家心仪的公司A面试,结果到了以后发现职位不匹配。但是小明不甘心,就告诉HR什么时候有匹配的岗位就告诉我,天荒地老我都等,HR说那也行,你关注一下我们公司的公众号,有消息我就发在那个上了。同时又有小红、小李都来面试,情况也和小明一样。HR心想随便你们多少人,我到时候在公众号上发一下消息就行了,到时候你们爱来不来。那当然了小明也不管你HR到底是谁,只要你有岗位发消息告诉我就行。

在上述栗子中,HR就是发布者,求职者就是订阅者

基础版

let corp = {};   // 自定义一个公司对象
// 这里放一个列表用来缓存回调函数
corp.list = [];
// 去订阅事件
corp.on = function (fn) {
    // 二话不说,直接把fn先存到列表中
    this.list.push(fn);
};
// 发布职位
corp.emit = function () {
    // 当发布的时候再把列表里存的函数依次执行
    this.list.forEach(cb => {
        cb.apply(this, arguments);
    });
};
// 小明关注公众号
corp.on(function (position, salary) {
    console.log('需求职位是:' + position);
    console.log('岗位薪水:' + salary);
});
// 小红关注公众号
corp.on(function(position, salary) {
    console.log('新的需求职位是:' + position);
    console.log('新的岗位薪水:' + salary);
});

corp.emit('前端', 10000);
corp.emit('设计', 6000);


需求职位是:前端
岗位薪水:10000
新的需求职位是:前端
新的岗位薪水:10000
需求职位是:设计
岗位薪水:6000
新的需求职位是:设计
新的岗位薪水:6000

很明显这样的结果并不是我们想要的,对于小明来说只想知道和前端相关的信息,小红也只关注设计的岗位,简单改造一下

改进版

let corp = {};
// 这次换成一个对象类型的缓存列表
corp.list = {};

corp.on = function(key, fn) {
    // 如果对象中没有对应的key值
    // 也就是说明没有订阅过
    // 那就给key创建个缓存列表
    if (!this.list[key]) {
        this.list[key] = [];
    }
    // 把函数添加到对应key的缓存列表里
    this.list[key].push(fn);
};
corp.emit = function() {
    // 第一个参数是对应的key值
    // 直接用数组的shift方法取出
    let key = [].shift.call(arguments),
        fns = this.list[key];
    // 如果缓存列表里没有函数就返回false
    if (!fns || fns.length === 0) {
        return false;
    }
    // 遍历key值对应的缓存列表
    // 依次执行函数的方法
    fns.forEach(fn => {
        fn.apply(this, arguments);
    });
};

// 小明关注公众号
corp.on('join', (position, salary) => {
    console.log('需求职位是:' + position);
    console.log('岗位薪水:' + salary);
});
// 小红关注公众号
corp.on('other', (skill, hobby) => {
    console.log('新的需求职位是:' + skill);
    console.log('新的岗位薪水:' + hobby);
});
corp.emit('join', '前端', 10000);
corp.emit('other', '设计', 6000);


需求职位是:前端
岗位薪水:10000
新的需求职位是:设计
新的岗位薪水:6000

最终版

let event = {
    list: {},
    on(key, fn) {
        if (!this.list[key]) {
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    emit() {
        let key = [].shift.call(arguments),
            fns = this.list[key];

        if (!fns || fns.length === 0) {
            return false;
        }
        fns.forEach(fn => {
            fn.apply(this, arguments);
        });
    },
    remove(key, fn) {
        // 这回我们加入了取消订阅的方法
        let fns = this.list[key];
        // 如果缓存列表中没有函数,返回false
        if (!fns) return false;
        // 如果没有传对应函数的话
        // 就会将key值对应缓存列表中的函数都清空掉
        if (!fn) {
            fns && (fns.length = 0);
        } else {
            // 遍历缓存列表,看看传入的fn与哪个函数相同
            // 如果相同就直接从缓存列表中删掉即可
            fns.forEach((cb, i) => {
                if (cb === fn) {
                    fns.splice(i, 1);
                }
            });
        }
    }
};

function xiaoming(data) {
	console.log('xiao明收到消息');
    console.log(data);
}
function xiaohong(data) {
	console.log('xiao红收到消息');
	console.log(data);
}

// 订阅消息
event.on('pet', xiaoming);
event.on('pet', xiaohong);
// 小红取消订阅
event.remove('pet', xiaohong);
// 发布
event.emit('pet', ['哈哈', '呵呵']);


xiao明收到消息
["哈哈", "呵呵"]

面向对象的写一遍

/**
 * 发布订阅模式(观察者模式)
 * 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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值