愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

JavaScript设计模式——观察者模式

观察者模式是一种常用的设计模式,它的好处是可以解耦两个对象之间的信息传递。

场景

某购物网站展示用户名、头像、获取购物车数据等等操作,需要在登录完成后进行。假设现在展示用户名、头像、获取购物车数据等操作的方法名称如下:

showUsername()
showAvatar()
getCartData()

但是登录是异步的,通过ajax的。这时候,一种简单的做法是:

ajax.success(function(data) {
    showUsername(data.userName)
    showAvatar(data.avatar)
    getCartData(data.uid)
})

那么,假如某一天我们又新增了一个功能,这种情况下,就需要去改动ajax登录后的回调函数,随着新增的功能越来越多,那么改动回调函数的次数也将越来越多,怎么办呢?


观察者模式

观察者模式,又称发布/订阅模式。其中,包含有发布者的角色(发布消息),订阅者的角色(关心消息的人)。使用观察者模式的情况下,我们就无需担心一个对象的改动的同时,还需要改动另外的对象了。发布者发布消息,不用关心谁对消息感兴趣,尽管发布就可以了。
实现:

const $ = (function() {
    const describers = []
    const on = function(eventName, fn) {
        if (!describers[eventName]) {
            describers[eventName] = []
        }
        describers[eventName].push(fn)
    }
    const emit = function() {
        const eventName = Array.prototype.shift.call(arguments)
        const list = describers[eventName]
        if (!list || list.length === 0) {
            return false
        }
        for (let i=0, fn; fn = list[i]; ++i) {
            // 这里arguments为emit时带上的参数
            fn.apply(this, arguments)
        }
    }
    const off = function(eventName, fn) {
        const list = describers[eventName]
        if (!list || list.length === 0) {
            return false
        }
        for (let i=list.length-1; i>=0; --i) {
            const _fn = list[i]
            if (_fn === fn) {
                list.splice(i, 1)
            }
        }
    }

    return { on, off, emit }
})()

如此一来,我们就可以重写前面提到的购物网站的逻辑了:

// 登陆成功后
$.emit('loginSuccessful', userData)

而关心登陆成功信息的模块,只需要订阅这一个消息就可以了:

// 如showAvatar()
$.on('loginSuccessful', function(data) {
    showAvatar(data.avatar)
})