---- 淘宝优惠券 ----资源下载 ---域名问题某些图片和js资源无法访问,导致一些代码实例无法运行!(代码里gzui.net换成momen.vip即可)

设计模式-简单易懂的观察者模式(四)

前端开发 蚂蚁 96℃ 0评论

一、什么是观察者模式

当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。

二、实际场景

1. DOM 事件

开发过程中,最常见的观察者模式场景就是 DOM 事件函数,先看看代码:

document.body.addEventListener('click', () => {
    alert(2)
}, false)

body 节点被点击时,触发 alert(2),从观察者模式来解释,就是我们订阅了 bdoy 节点的点击事件,当点击事件触发,我们就会收到通知。

2. 网站登录

网站登录功能,想必大多数做过平台需求的同学都实现过,当网站中的不同模块,如Header模块/Nav模块/正文模块,都依赖登录后获取的用户数据时,该怎么去实现呢?

2.1 普通做法

先看代码:

login.succ((data => {
    header.setAvatar(data.avatar) // 设置头像
    nav.setAvatar(data.avatar) // 设置导航区的头像
    list.refresh() // 刷新列表
})

这样的代码是不是特别熟悉?把依赖的方法,放在回调函数中。上述就是在登录成功的回调函数中,添加了各模块的方法。这么做导致各个模块和登录模块高度耦合,当新增了一个地址栏模块时,不得不再次修改登录模块的回调函数,违反了开放-封闭原则。

2.2 观察者模式

用观察者模式,优化上述需求。
登录模块是一个订阅对象,Header模块/Nav模块/正文模块添加对登录模块的订阅,当登录模块发生改变时,通知各个订阅了登录模块的模块。代码如下:

// 登录模块js
// 登录成功后,发布“loginSucc”登录成功消息,并传递data数据
login.succ(data=> {
    login.trigger('loginSucc', data)
})

// header模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
    header.setAvatar(data.avatar)
})

// nav模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
    nav.setAvatar(data.avatar)
})

上述代码用观察者模式重构了网站登录功能,后续不管新增多少业务模块,依赖登录功能,都只需要在模块内新增对登录成功的订阅,无需再改动登录模块。

3. 双向数据绑定

双向数据绑定也可以通过观察者模式实现。
双向指的是视图 view 和模型 model,当视图发生改变时,模型也发生变化,同样,当模型发生改变,视图也跟着同步变化。
分为以下几个步骤实现:

3.1 新建发布-订阅对象

新建一个发布-订阅对象,用于发布消息,订阅消息。

  • subscrib:订阅函数,当其他对象添加订阅消息时,将回调函数 push 进 callbacks 对象数组中;
  • publish:发布函数,当发布消息时,触发 callbacks 中该消息对应的 callback.

    const Pubsub = {
    subscrib: function (ev, callback) {
        this._callbacks || (this._callbacks = {});
        (this._callbacks[ev] || (this._callbacks[ev] = [])).push(callback);
    },
    
    publish: function () {
        const args = [...arguments]
        const ev = args.shift()
    
        if (!this._callbacks) return
        if (!this._callbacks[ev]) return
    
        this._callbacks[ev].forEach(callback => {
            callback(...args)
        })
    }
    }

    3.2 ui 更新

    3.2.1 发布 ui 更新消息

    注册 documentkeyup / change 事件,当激活事件的 dom 元素拥有 data-bind 属性时,说明 ui 正在更新,发布 ui 更新消息通知订阅者。

    function eventHander (e) {
    const { target } = e
    const { value: propValue } = target
    
    const propNameWhole = target.getAttribute('data-bind')
    if (propNameWhole) {
        // 发布ui更新消息
        Pubsub.publish('ui-update-event', { propNameWhole, propValue })
    }
    }
    console.log(document.addEventListener)
    document.addEventListener('change', eventHander, false)
    document.addEventListener('keyup', eventHander, false)
3.2.2 订阅 model 更新消息

所有包含 data-bind 属性的 dom 元素,订阅 model 更新消息,当 model 更新时,ui 将会收到通知。

// 订阅model更新消息,更新后所有符合条件的dom节点都会收到通知,进行更新
Pubsub.subscrib('model-update-event', function ({propNameWhole, propValue}) {
    const elements = document.querySelectorAll(`[data-bind="${propNameWhole}"]`)

    elements.forEach(element => {
        const elementTagName = element.tagName.toLowerCase()
        const formTypeTagNames = ['input', 'select', 'textarea']
        if (formTypeTagNames.includes(elementTagName)) {
            element.value = propValue
        } else {
            element.innerHTML = propValue
        }
    })
})

3.3 model 更新

3.3.1 订阅 ui 更新消息

订阅 ui 更新消息,当 ui 更新时,触发 modal 更新。

class Bind {
    constructor () {
        this.modelName = ''
    }

    initModel ({ modelName }) {
        this.modelName = modelName

        // 订阅ui更新消息
        Pubsub.subscrib('ui-update-event', ({propNameWhole, propValue}) => {
            const [ , _propName] = propNameWhole.split('.')
            this.updateModalData(_propName, propValue)
        })
    }

    // xxx省略xxx

    updateModalData (propName, propValue) {
        const propNameWhole = `${this.modelName}.${propName}`
        // 发布model更新消息
        Pubsub.publish('model-update-event', { propNameWhole, propValue });
    }

}
3.3.2 发布model更新消息

model 更新时,发布 model 更新消息,此时,订阅了 model 更新消息的 ui,将得到通知。

class Bind {
    constructor () {
        this.modelName = ''
    }

    // xxx省略xxx

    loadModalData (modelData) {
        for (let propName in modelData) {
            this.updateModalData(propName, modelData[propName])
        }
    }

    updateModalData (propName, propValue) {
        const propNameWhole = `${this.modelName}.${propName}`
        // 发布model更新消息
        Pubsub.publish('model-update-event', { propNameWhole, propValue });
    }

}

完整源码见:github

三、总结

从上文的实际场景例子可见,观察者模式建立了一套触发机制,帮助我们完成更松耦合的代码编写。但是也不能过度使用,否则会导致程序难以追踪和理解。有以下场景适用于观察者模式:

一个抽象模型的一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

可通过github源码进行实操练习。希望本文能对你有所帮助,感谢阅读❤️~

作者:清汤饺子
链接:https://juejin.cn/post/6970724597029142564
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载请注明:有爱前端 » 设计模式-简单易懂的观察者模式(四)

喜欢 (0)or分享 (0)
发表我的评论
取消评论

表情