欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue源码学习笔记之实现双向数据绑定(一)--- 发布订阅模式

程序员文章站 2022-05-21 13:48:53
...

首先看下vue官方文档中对于响应式原理说明的配图:

Vue源码学习笔记之实现双向数据绑定(一)--- 发布订阅模式

Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式来实现双向数据绑定的。当把一个普通 Js 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,通过用Object.defineProperty 设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,通过setter在数据变更的时候通知订阅者更新视图。

发布—订阅模式

最近又看了一遍《javaScript设计模式与开发实践》中的发布订阅模式,正好结合Vue源码做个总结。

概念:发布订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

优点

1. 时间上的解耦:发布-订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。

 2. 对象之间的解耦:发布-订阅模式让两个对象松耦合地联系在一起,相互通信不再需要显示的调用另外一个对象的接口。

简单的发布-订阅模式的实现

1. 首先要指定好谁充当发布者;

2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者;

3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数;

发布订阅模式的通用实现

var event = {
  clientList: [],

  listen: function (key, fn) {
    // 初始化特定key属性的缓存队列
    if (!this.clientList[key]) {
      this.clientList[key] = []
    }
    // 订阅的消息添加进缓存列表
    this.clientList[key].push(fn)
  },

  trigger: function () {
    var key = Array.prototype.shift.call(arguments)
    var fns = this.clientList[key]

    // 如果没有绑定对应的消息
    if (!fns || fns.length === 0) {
      return false
    }

    for (var i = 0, fn; fn = fns[i++]; ){
      fn.apply(this, arguments) // arguments 是 trigger 时带上的参数
    }
  },

  remove: function (key, fn) {
    var fns = this.clientList[key]

    // 如果key对应的消息没有被人订阅,则直接返回
    if (!fns) {
      return false
    }

    // 如果没有传入具体的回调函数,则表示需要取消key对应消息的所有订阅
    if (!fn) {
      fns && (fns.length = 0)
    } else {
      // 反向遍历订阅的回调函数列表
      for (var l = fns.length - 1; l >= 0; l--) {
        var _fn = fns[l]
        if (_fn === fn) { // 删除订阅者的回调函数
          fns.splice(l, 1)
        }
      }
    }
  }
}

// installEvent函数:给所有的对象都动态添加发布—订阅功能
var installEvent = function( obj ){
  for ( var i in event ){
    obj[ i ] = event[ i ]
  }
}

// 测试:
var salesOffices = {}

installEvent(salesOffices)

salesOffices.listen('squereMeter88', fn1 = function(price) {
  console.log('价格=' + price)
})

salesOffices.listen('squereMeter100', fn2 = function(price) {
  console.log('价格=' + price)
})

salesOffices.remove('squereMeter88', fn1)
salesOffices.trigger('squereMeter100', 200)

全局的发布-订阅对象的实现:

订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会发送给哪些订阅者,Event 作为一个类似“中介者”的角色,把订阅者和发布者联系起来。

var Event = (function(){
  var clientList = {},
      listen,
      trigger,
      remove;

  listen = function( key, fn ){
    if ( !clientList[ key ] ){
      clientList[ key ] = [];
    }
    clientList[ key ].push( fn );
  };

  trigger = function(){
    var key = Array.prototype.shift.call( arguments ),
        fns = clientList[ key ];
    if ( !fns || fns.length === 0 ){
      return false;
    }
    for( var i = 0, fn; fn = fns[ i++ ]; ){
      fn.apply( this, arguments );
    }
  };

  remove = function( key, fn ){
    var fns = clientList[ key ];
    if ( !fns ){
      return false;
    }
    if ( !fn ){
      fns && ( fns.length = 0 );
    } else {
      for ( var l = fns.length - 1; l >=0; l-- ){
        var _fn = fns[ l ];
        if(_fn === fn){
          fns.splice( l, 1 );
        }
      }
    }
  };

  return {
    listen: listen,
    trigger: trigger,
    remove: remove
  }

})();

发布订阅模式实现EventEmitter:

通过 on 方法注册事件,trigger 方法触发事件,once 方法注册只触发一次的事件, off 方法注销事件。

class EventEmitter {
  constructor() {
    this.subs = {}
  }

  on(event, cb) {
    (this.subs[event] || this.subs[event] = []).push(cb)
  }

  trigger(event, ...args) {
    this.subs[event] && this.subs[event].forEach(cb => {
      cb(...args)
    })
  }

  once(event, cbOnce) {
    let cb = (...args) => {
      cbOnce(...args)
      this.off(event, cbOnce)
    }
    this.on(event, cb)
  }

  off(event, cbOff) {
    if (this.subs[event]) {
      let index = this.subs[event].findIndex(cb => cb === cbOff)
      this.subs[event].splice(index, 1)
      if (!this.subs[event].length) delete this.subs[event]
    }
  }
}