简单的订阅发布模式

const Vue = (function () {
    let uid = 0;
// 消息管理员,用于存储订阅者并发布消息
    class Dep {
        constructor () {
            this.id = uid++; // 设置ID,用于区分新watcher
            this.subs = []; // 存储订阅者的数组
        }
        depend () { // 触发target上的Watcher中的addDep方法,参数为dep的实例本身,方法作用就是将watcher放进subs数组
            Dep.target.addDep(this);
        }
        addSub (sub) { // 添加订阅者
            this.subs.push(sub);
        }
        notify () {
            this.subs.forEach(sub => sub.update());
        }
    }
    Dep.target = null;
// 监听者,监听对象属性值的变化
    class Observer {
        constructor (value) {
            this.value = value;
            this.walk(value);
        }
        walk (value) { // 遍历属性值并监听
            Object.keys(value).forEach(key => this.convert(key, value[key]))
        }
        convert (key, val) { // 执行监听的具体方法
            defineReactive(this.value, key, val);
        }
    }
    function defineReactive (obj, key, val) {
        const dep = new Dep(); // 一个属性一个消息管理器?
        let childOb = observer(val);
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: () => {
                console.log('我不是get了吗');
                // 如果Dep类存在target属性,将其添加至dep实例的subs数组中,target指向一个watcher实例
                // 每个watcher都是一个订阅者,watcher实例在实例化的过程中,会读取data中的某个属性,从而触发当前的get方法
                if (Dep.target) {
                    dep.depend();
                }
                return val;
            },
            set: newVal => {
                console.log('我不是set了吗', newVal, val);
                if (newVal !== val) { // 发生改变了,可能由一个普通变量转变为对象,所以需要继续监听
                    val = newVal;
                    childOb = observer(newVal);
                    dep.notify();
                }
            }
        })
    }
    function observer (val) {
        if (val && typeof val === 'object') {
            return new Observer(val);
        }
        return '';
    }
// 订阅者,指的是它需要知道某个对象发生改变而做出反应,需要先订阅
    class Watcher {
        constructor (vm, expOrFn, callback) {
            this.depIds = {}; // hash储存订阅者的ID,避免重复的订阅者
            this.vm = vm; // 被订阅的数据一定要来自于当前的vue实例
            this.callback = callback; // 回调函数
            this.exOrFn = expOrFn; // 被订阅的数据
            this.val = this.get(); // 维护更新之前的数据
        }
        // 订阅中心在监听到数据发生改变时会触发notify方法,方法中调用update方法
        update () {
            this.run();
        }
        run () {
            let val = this.get();
            this.val = val;
            this.callback.call(this.vm, val);
        }
        addDep (dep) {
            // 如果在depIds中的hash中没有当前ID,可以判断是新的Watcher,因此可以添加到dep的数组中存储,此判断是为了避免同ID的watcher被多次存储
            // 一个新的watcher对象只可以被一个订阅中心储存一次
            if (!this.depIds.hasOwnProperty(dep.id)) {
                dep.addSub(this);
                this.depIds[dep.id] = dep;
            }
        }
        get () {
            // 将当前的watcher传到Dep.target属性里面,方便获取
            Dep.target = this;
            const val = this.vm._data[this.exOrFn]; // 触发了observer的get方法,并将当前订阅者存放到了sups数组
            Dep.target = null; // 置空
            return val;
        }
    }
    class vue {
        constructor (options = {}) {
            let data = (this._data = options.data); // vue绑定的数据
            this.methods = options.methods; // vue绑定的方法
            Object.keys(data).forEach(key => this._proxy(key)) // 将data的属性都代理到vue实例上,这样可以监听到他的改变
            observer(data);
        }
        $watch (expOrFn, cb) {
            new Watcher(this, expOrFn, cb)
        }
        _proxy (key) {
            Object.defineProperty(this, key, {
                configurable: true,
                enumerable: true,
                get: () => {
                    return this._data[key]
                },
                set: val => {
                    this._data[key] = val;
                }
            })
        }
    }
    return vue
})();

let demo = new Vue({
    data: {
        text: '',
    },
});

const p = document.getElementById('p');
const input = document.getElementById('input');

input.addEventListener('keyup', function(e) {
    demo._data.text = e.target.value;
});

demo.$watch('text', str => p.innerHTML = str);
// 基于proxy升级的订阅发布模式
const Vue = (function () {
  let uid = 0

  // 用于储存订阅者并发布消息
  class Dep {
    constructor () {
      // 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher
      this.id = uid++
      // 储存订阅者的数组
      this.subs = []
    }

    // 触发target上的Watcher中的addDep方法,参数为dep的实例本身
    depend () {
      Dep.target.addDep(this)
    }

    // 添加订阅者
    addSub (sub) {
      this.subs.push(sub)
    }

    notify () {
      // 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
      this.subs.forEach(sub => sub.update())
    }
  }

  // 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher
  Dep.target = null

  // 监听者,监听对象属性值的变化
  class Observer {
    constructor (value) {
      this.value = value
      this.walk(value)
    }

    // 遍历属性值并监听
    walk (value) {
      Object.keys(value).forEach(key => this.convert(key, value[key]))
    }

    // 执行监听的具体方法
    convert (key, val) {
      defineReactive(this.value, key, val)
    }
  }

  function defineReactive (obj, key, val) {
    const dep = new Dep()
    // 给当前属性的值添加监听
    let chlidOb = observe(val)
    if (typeof val === 'object') {
      obj[key] = new Proxy(val, {
        set (target, property, val2, receiver) {
          if (val === val2) return
          // 对新值进行监听
          chlidOb = observe(val2)
          // 通知所有订阅者,数值被改变了
          let returnData = Reflect.set(target, property, val2, receiver)
          dep.notify()
          return returnData
        },
        get (target, key, receiver) {
          if (Dep.target) {
            dep.depend()
          }
          return Reflect.get(target, key, receiver)
        }
      })
    } else {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: () => {
          // 如果Dep类存在target属性,将其添加到dep实例的subs数组中
          // target指向一个Watcher实例,每个Watcher都是一个订阅者
          // Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
          if (Dep.target) {
            dep.depend()
          }
          return val
        },
        set: newVal => {
          if (val === newVal) return
          val = newVal
          // 对新值进行监听
          chlidOb = observe(newVal)
          // 通知所有订阅者,数值被改变了
          dep.notify()
        }
      })
    }
  }

  function observe (value) {
    // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
    if (!value || typeof value !== 'object') {
      return
    }
    return new Observer(value)
  }

  class Watcher {
    constructor (vm, expOrFn, cb) {
      this.depIds = {} // hash储存订阅者的id,避免重复的订阅者
      this.vm = vm // 被订阅的数据一定来自于当前Vue实例
      this.cb = cb // 当数据更新时想要做的事情
      this.expOrFn = expOrFn // 被订阅的数据
      this.val = this.get() // 维护更新之前的数据
    }

    // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
    update () {
      this.run()
    }

    addDep (dep) {
      // 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存
      // 此判断是避免同id的Watcher被多次储存
      if (!this.depIds.hasOwnProperty(dep.id)) {
        dep.addSub(this)
        this.depIds[dep.id] = dep
      }
    }

    run () {
      const val = this.get()
      this.val = val
      this.cb.call(this.vm, val)
    }

    get () {
      // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
      Dep.target = this
      let val = this.vm._data[this.expOrFn] // 触发一个get
      if (typeof val === 'object') { // 没有被get到,没存进去
        if (Object.prototype.toString.call(val) === '[object Array]') {
          val = Object.assign([], this.vm._data[this.expOrFn])
        } else if (Object.prototype.toString.call(val) === '[object Object]') {
          val = Object.assign({}, this.vm._data[this.expOrFn])
        }
      }
      Dep.target = null
      return val
    }
  }

  class Vue {
    constructor (options = {}) {
      // 简化了$options的处理
      this.$options = options
      // 简化了对data的处理
      let data = (this._data = this.$options.data)
      // 监听数据
      observe(this._data)
      // 将所有data最外层属性代理到Vue实例上
      Object.keys(this._data).forEach(key => this._proxy(key, this._data[key]))
    }

    // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
    $watch (expOrFn, cb) {
      new Watcher(this, expOrFn, cb)
    }

    //  obj[key] = new Proxy(value,
    // defineObjectReactive(data, k, data[k])
    _proxy (key, value) {
      let _this = this
      if (typeof value === 'object') {
        this[key] = new Proxy(value, {
          set (target, key, val, receiver) {
            return Reflect.set(target, key, val, receiver)
          },
          get (target, key, receiver) {
            return Reflect.get(target, key, receiver)
          }
        })
      } else {
        Object.defineProperty(this, key, {
          configurable: true,
          enumerable: true,
          get: () => this._data[key],
          set: val => {
            this._data[key] = val
          },
        })
      }
    }
  }

  return Vue
})()

let demo = new Vue({
  data: {
    text: '',
    list: []
  },
})
const p = document.getElementById('p')
const input = document.getElementById('input')

input.addEventListener('keyup', function (e) {
  demo.text = e.target.value
})

demo.$watch('text', str => p.innerHTML = str)

const list = document.getElementById('list')
const btn = document.getElementById('btn')

btn.addEventListener('click', function () {
  demo.list.push(1)
})

const render = arr => {
  const fragment = document.createDocumentFragment()
  if (document.querySelectorAll('li').length === arr.length) { // 说明只是改了数据内容,没改长度
    let spanArray = document.querySelectorAll('span')
    for (let i = 0; i < arr.length; i++) {
      spanArray[i].innerHTML = arr[i]
    }
  } else {
    let li = document.createElement('li')
    let span = document.createElement('span')
    span.innerHTML = arr[arr.length - 1]
    let input = document.createElement('input')
    input.value = arr[arr.length - 1]
    input.className = 'testInput'
    li.appendChild(span)
    li.appendChild(input)
    fragment.appendChild(li)
    list.appendChild(fragment)
    let allInput = document.querySelectorAll('.testInput')
    allInput[arr.length - 1].oninput = function (e) {
      demo.list[arr.length - 1] = e.target.value
    }
  }
}

demo.$watch('list', list => {
  render(list)
})

 

模式有什么区别?它们的应用场景有哪些? 回答: 订阅发布模式和观察者模式在原理上是相似的,都是建立了一种对象间的依赖关系,当被观察者或发布者的状态发生变化时,观察者或订阅者会得到通知并进行相应的更新。\[1\]区别在于订阅发布模式中存在一个订阅中心,而观察者模式中没有。在订阅发布模式中,发布者和订阅者之间互不干扰,由调度中心进行处理,实现了解耦和更细粒度的控制。\[2\]\[3\]观察者模式则更加简单直接,没有中间的调度中心。 这两种模式的应用场景也有一些差异。观察者模式适用于一对多的场景,当一个对象的状态变化需要通知多个观察者时,可以使用观察者模式。而订阅发布模式适用于多对多的场景,当多个发布者和多个订阅者之间存在复杂的依赖关系时,可以使用订阅发布模式。\[3\]此外,订阅发布模式还可以实现一些细粒度的控制,比如权限控制和节流操作。 #### 引用[.reference_title] - *1* *2* [观察者模式发布订阅模式](https://blog.csdn.net/weixin_50290816/article/details/128379200)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [发布订阅模式与观察者模式](https://blog.csdn.net/hf872914334/article/details/88899326)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值