JS-观察者模式

你。 提交于 2019-11-30 13:20:59

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

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

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

介绍

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

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

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

优点

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

缺点

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

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

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

实现

基础版

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明收到消息
["哈哈", "呵呵"]

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!