vue中的观察者模式和发布订阅者模式

Posted 过鹿人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue中的观察者模式和发布订阅者模式相关的知识,希望对你有一定的参考价值。


观察者模式

目标者对象和观察者对象有相互依赖的关系,观察者对某个对象的状态进行观察,如果对象的状态发生改变,就会通知所有依赖这个对象的观察者,

目标者对象 Subject,拥有方法:添加 / 删除 / 通知 Observer;

观察者对象 Observer,拥有方法:接收 Subject 状态变更通知并处理;

目标对象 Subject 状态变更时,通知所有 Observer。

 

Vue中响应式数据变化是观察者模式 每个响应式属性都有dep,dep存放了依赖这个属性的watcher,watcher是观测数据变化的函数,如果数据发生变化,dep就会通知所有的观察者watcher去调用更新方法。因此, 观察者需要被目标对象收集,目的是通知依赖它的所有观察者。那为什么watcher也要存放dep呢?是因为当前正在执行的watcher需要知道此时是哪个dep通知了自己。

 

在beforeCreate之后,created之前调用observe(data)初始化响应式数据,以下是简化版代码(没有处理数组的劫持)
class Observer {
    // 需要对value的属性描述重新定义
    constructor(value) {
      this.walk(value); // 初始化的时候就对数据进行监控
    }
    walk(data) {
      Object.keys(data).forEach((key) => {
        defineReactive(data, key, data[key]);
      });
    }
  }
  
  function defineReactive(data, key, value) {
    // value 可能是一个对象,要递归劫持,所以数据不能嵌套太深
    observe(value);
    let dep = new Dep();
    Object.defineProperty(data, key, {
      get() {
        // 如果有 watcher,就让 watcher 记住 dep,防止产生重复的 dep, 同时 dep 也收集此 watcher
        if (Dep.target) {
          dep.depend();
        }
        return value;
      },
      set(newVal) {
        // 数据没变动则不处理
        if (value === newVal) return;
        observe(newVal); // 如果新值是个对象,递归拦截
        value = newVal; // 设置新的值
        dep.notify(); // 通知收集的 watcher 去更新
      },
    });
}
function observe(data) {
    // 不是对象则不处理,isObject是用来判断是否为对象的函数
    if (Object.prototype.toString.call(data)!== \'[object Object]\') return;
    // 通过类来实现数据的观测,方便扩展,生成实例
    return new Observer(data);
}
observe(data)
 
在created之后,mouted之前调用mountComponent挂载组件,以下是简化版代码(没有处理watch和computed的watcher)
class Dep {
    static target = null
    constructor() {
      this.id = id++;
      this.subs = []; // 存放依赖的watcher
    }
    depend() {
      // 让正在执行的watcher记录dep,同时dep也会记录watcher
      Dep.target.addDep(this);
    }
    addSub(watcher) {
      // 添加观察者对象
      this.subs.push(watcher);
    }
    notify() {
      // 触发观察者对象的更新方法
      this.subs.forEach((watcher) => watcher.update());
    }
}
class Watcher {
    constructor(vm, exprOrFn) {
      this.vm = vm;
      this.deps = [];
      // 用来去重,防止多次取同一数据时存入多个相同dep
      this.depId = new Set();
      // exprOrFn是updateComponent
      this.getter = exprOrFn;
      // 更新页面
      this.get();
    }
    get() {
      Dep.target = watcher; // 取值之前,收集 watcher
      this.getter.call(this.vm); // 调用updateComponent更新页面
      Dep.target = null; // 取值完成后,将 watcher 删除
    }
    // dep.depend执行时调用
    addDep(dep) {
        let id = dep.id;
        let has = this.depId.has(id);
        if (!has) {
            this.depId.add(id);
            // watcher存放dep
            this.deps.push(dep);
            // dep存放watcher
            dep.addSub(this);
        }
    }  
    // 更新页面方法,dep.notify执行时调用
    update() {
        this.get(); // 一修改数据就渲染更新
    }
}
function mountComponent(vm) {
    // 渲染更新页面
    let updateComponent = () => {
      let vnode = vm._render(); // 生成虚拟节点 vnode
      vm._update(vnode); // 将vnode转为真实节点
    };
    // 每个组件都要调用一个渲染 watcher
    new Watcher(vm, updateComponent);
}
mountComponent(vm)

 

发布订阅模式

基于一个事件中心,接收通知的对象是订阅者,需要 先订阅某个事件,触发事件的对象是发布者,发布者通过触发事件,通知各个订阅者。 js中事件绑定,就是发布订阅模式

发布订阅模式相比观察者模式多了个事件中心,订阅者和发布者不是直接关联的。

 

vue中的事件总线就是使用的发布订阅模式

// 事件总线
class Bus {
  constructor() {
    // 用来记录事件和监听该事件的数组
    this.listeners = {};
  }
  // 添加指定事件的监听者
  $on(eventName, handler) {
    this.listeners[eventName].add(handler);
  }
  // 取消监听事件
  $off(eventName, handler) {
    this.listeners[eventName].delete(handler);
  }
  // 触发事件
  $emit(eventName, ...args) {
    this.listeners[eventName].forEach((fn) => fn(...args));
  }
}

事件总线的具体使用可以查看这篇vue之事件总线

 

观察者模式和发布订阅模式的区别

目标和观察者之间是互相依赖的。

发布订阅模式是由统一的调度中心调用,发布者和订阅者不知道对方的存在。

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

发布订阅和观察者模式

曾经我一直以为发布订阅和观察者是一回事,而且有些书和博客里面也是这样说的,直到我看到了这篇剖析 Vue 原理&实现双向绑定 MVVM的文章,作者很细致的解读了 vue 的双向绑定实现原理,并用简约的代码实现了一个简化版的 vue,包括了解析器 Compile ,监听器 Observer,订阅者 Watcher 和 一个 vue 构造函数. 虽然很简介,但是我一直搞的不是很明白,对于发布订阅加数据劫持的原理是懂非懂,最近又偶然会看到这篇文章Observer vs Pub-Sub pattern,再结合上面的文章才算真的理解了发布订阅加数据劫持实现双向绑定的原理。

发布订阅模式 (wiki )

发布订阅最大的优点就是,发布者(Publisher)和订阅者(Subscriber)解耦,发布者发布消息到一个中间的消息代理,然后订阅者向该消息代理注册订阅,由消息代理来进行过滤。消息代理通常执行存储转发的功能将消息从发布者发送到订阅者。
技术图片

class Publisher {
  // 消息发布
  constructor() {
    this.MiddleProxy = new MiddleProxy();
  }
  register(action) {
    // 在一定条件下去注册一个
    if (action === ‘needer‘) {
      const subscriber = new Subscriber(action, () => {
        console.log(action);
      });
      this.MiddleProxy.addSub(subscriber);
    }
  }

  publish(action) {
    this.MiddleProxy.notify(action);
  }
}

class MiddleProxy {
  // 消息代理中间件
  constructor() {
    this.subs = []; // 存放订阅者的实例
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  notify(action) {
    this.subs.filter(sub => (sub.action === action)).forEach(sub => sub.run());
  }
}

class Subscriber {
  // 订阅者
  constructor(action, callback) {
    this.action = action;
    this.callback = callback;
  }

  run() {
    this.callback();
  }
}

观察者模式(wiki

观察者模式中消息的发布者(Subject)和观察者(Observer)是紧耦合的关系,在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。
技术图片

class Subject {
  constructor() {
    this._observers = [];
  }

  subscribe(observer) {
    this._observers.push(observer);
  }

  unsubscribe(observer) {
    this._observers = this._observers.filter(obs => observer !== obs);
  }

  fire(change) {
    this._observers.forEach(observer => {
      observer.update(change);
    });
  }
}

class Observer {
  constructor(state) {
    this.state = state;
    this.initialState = state;
  }

  update(change) {
    let state = this.state;
    switch (change) {
      case ‘INC‘:
        this.state = ++state;
        break;
      case ‘DEC‘:
        this.state = --state;
        break;
      default:
        this.state = this.initialState;
    }
  }
}

比较

发布订阅模式和观察者最大的区别就是,发布订阅模式的消息的发布者和消息的订阅者之间的关系,发布订阅是提供一个中间消息代理,通过消息代理实现订阅者的注册,并通过中间件来广播订阅者,而观察者模式是消息的发布者直接存储订阅者实例,直接触发订阅者的更新
技术图片

发布订阅模式的优缺点都在于发布者和订阅者的解耦上
优点

  • 实现时间上的解耦(组件,模块之间的异步通讯)
  • 对象之间的解耦,交由发布订阅的对象管理对象之间的耦合关系

缺点

  • 创建订阅者本身会消耗内存,订阅消息后,也许,永远也不会有发布,而订阅者始终存在内存中
  • 对象之间解耦的同时,他们的关系也会被深埋在代码背后,这会造成一定的维护成本




以上是关于vue中的观察者模式和发布订阅者模式的主要内容,如果未能解决你的问题,请参考以下文章

js设计模式-观察者模式来模拟vue的双向数据绑定

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

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

考验手写!透析观察者模式和发布订阅模式

设计模式 行为型模式 -- 观察者模式(发布-订阅(Publish/Subscribe)模式)

从发布-订阅模式到Vue响应系统