美文网首页vue的响应式
JS设计模式之订阅发布模式

JS设计模式之订阅发布模式

作者: Splendid飞羽 | 来源:发表于2021-05-09 21:48 被阅读0次

    定义:订阅发布模式定义了对象间一种一对多的关系,让多个观察者对象同时监听同一主题对象,当一个对象改变时,所有依赖于它的对象都将得到通知。

    优点:

    • 支持单一广播通信,当对象的状态发生变化时,会自动通知订阅它的对象。
    • 发布者与订阅者的耦合性降低,发布者只关注发布的消息,订阅者只关注订阅的消息

    缺点

    • 消耗一定的时间和内存
    • 可以弱化对象之间的联系,但是过度使用会造成代码难以维护和理解。

    核心:订阅者、发布者

    1、实现原理:
    • 确定谁是发布者
    • 给发布者增加一个缓存列表,用来缓存回调函数来通知订阅者
    • 最后就是发布消息,发布者遍历这个列表,依次触发里面存放的订阅者的回调函数,来通知订阅者。

    下面是一个简单的订阅发布的js示例

    var shoeObj = {}; // 定义发布者
    shoeObj.list = []; // 缓存列表 存放订阅者回调函数
            
    // 增加订阅者
    shoeObj.listen = function(fn) {
        shoeObj.list.push(fn);  // 订阅消息添加到缓存列表
    }
    
    // 发布消息
    shoeObj.trigger = function(){
        for(var i = 0,fn; fn = this.list[i++];) {
            fn.apply(this,arguments); 
        }
    }
    // 小红订阅如下消息
    shoeObj.listen(function(color,size){
        console.log("颜色是:"+color);
        console.log("尺码是:"+size);  
    });
    
    // 小花订阅如下消息
    shoeObj.listen(function(color,size){
        console.log("再次打印颜色是:"+color);
        console.log("再次打印尺码是:"+size); 
    });
    shoeObj.trigger("红色",40);
    shoeObj.trigger("黑色",42);
    // 颜色是:红色
    // 尺码是:40
    // 再次打印颜色是:红色
    // 再次打印尺码是:40
    // 颜色是:黑色
    // 尺码是:42
    // 再次打印颜色是:黑色
    // 再次打印尺码是:42
    

    参考我的印象笔记

    2、源码中的发布-订阅模式**

    发布-订阅模式在源码中应用很多,特别是现在很多前端框架都会有的双向绑定机制的场景,这里以现在很火的 Vue 为例,来分析一下 Vue 是如何利用发布-订阅模式来实现视图层和数据层的双向绑定。先借用官网的双向绑定原理图:

    image

    下面稍微解释一下这个图(框架源码整个过程比较复杂,如果现在看不懂下面几段也没关系,大致了解一下即可)。

    组件渲染函数(Component Render Function)被执行前,会对数据层的数据进行响应式化。响应式化大致就是使用 Object.defineProperty 把数据转为 getter/setter,并为每个数据添加一个订阅者列表的过程。这个列表是 getter 闭包中的属性,将会记录所有依赖这个数据的组件。

    • 也就是说,响应式化后的数据相当于发布者。
    • 每个组件都对应一个 Watcher 订阅者。当每个组件的渲染函数被执行时,都会将本组件的 Watcher 放到自己所依赖的响应式数据的订阅者列表里,这就相当于完成了订阅,一般这个过程被称为依赖收集(Dependency Collect)。
    • 组件渲染函数执行的结果是生成虚拟 DOM 树(Virtual DOM Tree),这个树生成后将被映射为浏览器上的真实的 DOM 树,也就是用户所看到的页面视图。
    • 当响应式数据发生变化的时候,也就是触发了 setter 时,setter 会负责通知(Notify)该数据的订阅者列表里的 WatcherWatcher 会触发组件重渲染(Trigger re-render)来更新(update)视图。

    我们可以看看 Vue 的源码:

    // src/core/observer/index.js 响应式化过程
    
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            // ...
            const value = getter ? getter.call(obj) : val // 如果原本对象拥有getter方法则执行
            dep.depend()                     // 进行依赖收集,dep.addSub
            return value
        },
        set: function reactiveSetter(newVal) {
            // ...
            if (setter) { setter.call(obj, newVal) }    // 如果原本对象拥有setter方法则执行
            dep.notify()               // 如果发生变更,则通知更新
        }
    })
    
    

    而这个 dep 上的 dependnotify 就是订阅和发布通知的具体方法。

    • 简单来说,响应式数据是消息的发布者,而视图层是消息的订阅者,如果数据更新了,那么发布者会发布数据更新的消息来通知视图更新,从而实现数据层和视图层的双向绑定。

    3、发布-订阅模式的优缺点

    发布-订阅模式最大的优点就是解耦:

    • 时间上的解耦 :注册的订阅行为由消息的发布方来决定何时调用,订阅者不用持续关注,当消息发生时发布者会负责通知;
    • 对象上的解耦 :发布者不用提前知道消息的接受者是谁,发布者只需要遍历处理所有订阅该消息类型的订阅者发送消息即可(迭代器模式),由此解耦了发布者和订阅者之间的联系,互不持有,都依赖于抽象,不再依赖于具体;
    • 由于它的解耦特性,发布-订阅模式的使用场景一般是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变。发布-订阅模式还可以帮助实现一些其他的模式,比如中介者模式。

    发布-订阅模式也有缺点:

    • 增加消耗 :创建结构和缓存订阅者这两个过程需要消耗计算和内存资源,即使订阅后始终没有触发,订阅者也会始终存在于内存;
    • 增加复杂度 :订阅者被缓存在一起,如果多个订阅者和发布者层层嵌套,那么程序将变得难以追踪和调试,参考一下 Vue 调试的时候你点开原型链时看到的那堆 deps/subs/watchers 们…
    • 缺点主要在于理解成本、运行效率、资源消耗,特别是在多级发布-订阅时,情况会变得更复杂。
    4、发布订阅模式与其他设计模式的区别

    发布-订阅模式和观察者模式

    观察者模式与发布-订阅者模式,在平时你可以认为他们是一个东西,但是某些场合(比如面试)下可能需要稍加注意,借用网上一张流行的图:

    image

    区别主要在发布-订阅模式中间的这个 Event Channel:

    • 观察者模式 中的观察者和被观察者之间还存在耦合,被观察者还是知道观察者的;
    • 发布-订阅模式 中的发布者和订阅者不需要知道对方的存在,他们通过消息代理来进行通信,解耦更加彻底;

    发布-订阅模式和责任链模式

    发布-订阅模式和责任链模式也有点类似,主要区别在于:

    • 发布-订阅模式 传播的消息是根据需要随时发生变化,是发布者和订阅者之间约定的结构,在多级发布-订阅的场景下,消息可能完全不一样;
    • 责任链模式 传播的消息是不变化的,即使变化也是在原来的消息上稍加修正,不会大幅改变结构;

    任务:手写发布订阅模式

    相关文章

      网友评论

        本文标题:JS设计模式之订阅发布模式

        本文链接:https://www.haomeiwen.com/subject/ndpvdltx.html