Просмотр режима наблюдателя из исходного кода vue

исходный код Шаблоны проектирования Vue.js

Аннотация: Цикл статей о паттернах проектирования интерпретации исходного кода будет обновляться одна за другой~

Шаблон наблюдателя

Прежде всего, мы должны спросить себя, что такое паттерн наблюдателя?

концепция

Шаблон наблюдателя (наблюдатель): также известен как шаблон публикации-подписчика. Он определяет отношение зависимости «один ко многим», то есть при изменении состояния объекта все объекты, которые зависят от него, будут уведомлены и автоматически обновлены, что устраняет функциональную связь между объектом-субъектом и наблюдателем.

Расскажи историю

Вышеупомянутая концепция шаблона наблюдателя может быть более официальной, поэтому давайте расскажем историю, чтобы понять ее.

  • О: Это шпион, кодовое имя 001 (издатель).
  • B: является корреспондентом, ответственным за секретную передачу с A (абонентом)
  1. Ежедневная работа А состоит в том, чтобы собрать некоторую информацию о яркой стороне
  2. B отвечает за темновое наблюдение A
  3. После того, как A передает некоторые релевантные сообщения (чаще сообщение необходимо инкапсулировать и передать, а конкретный анализ будет основан на исходном коде позже)
  4. B немедленно подпишется на сообщение, а затем внесет некоторые соответствующие изменения, такие как уведомление о выполнении некоторых действий для обработки некоторых действий.

применимость

Шаблон Observer можно использовать в любом из следующих сценариев.

  1. Когда абстрактная модель имеет два аспекта, один зависит от другого. Инкапсуляция двух в отдельные объекты позволяет их независимо изменять и повторно использовать.
  2. При изменении одного объекта необходимо одновременно изменить и другие объекты, но неизвестно, сколько объектов нужно изменить.
  3. Когда объект должен уведомить другие объекты, но не знает, кто этот конкретный объект. Другими словами, вы не хотите, чтобы эти объекты были тесно связаны.

Использование vue для шаблона Observer

vueЕсть много мест, где можно использовать шаблон наблюдателя, здесь мы в основном говорим об инициализации данных.

var vm = new Vue({
  data () {
    return {
      a: 'hello vue'
    }
  }
})

1. Реализовать захват данных

Выше мы видим, чтоvueиспользуетObject.defineProperty()Взлом данных. И инкапсулировать слой транзитной станции при изменении передачи данных, то есть то, что мы видимDepа такжеWatcherдва класса.

В этом разделе мы рассмотрим только то, как перехватить данные с помощью шаблона наблюдателя.

1.1, рекурсивный обход

мы все знаем,vueдляdataВсе данные в нем захвачены, он может только пройти через объект, чтобы завершить захват каждого атрибута.Исходный код выглядит следующим образом

walk (obj: Object) {
  const keys = Object.keys(obj)
  // 遍历将其变成 vue 的访问器属性
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}

1.2. Публикация/подписка

Из обхода вышеуказанных объектов мы видимdefineReactive, то наиболее критической точкой угона также является эта функция, которая инкапсулируетgetterа такжеsetterФункция, использующая шаблон наблюдателя, слушающая друг друга

// 设置为访问器属性,并在其 getter 和 setter 函数中,使用发布/订阅模式,互相监听。
export function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
  // 这里用到了观察者(发布/订阅)模式进行了劫持封装,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
  // 实例化一个主题对象,对象中有空的观察者列表
  const dep = new Dep()
  
  // 获取属性描述符对象(更多的为了 computed 里面的自定义 get 和 set 进行的设计)
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get
  const setter = property && property.set
  
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 收集依赖,建立一对多的的关系,让多个观察者监听当前主题对象
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // 这里是对数组进行劫持
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // 劫持到数据变更,并发布消息进行通知
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

1.3. Возврат экземпляра Observer

выше мы видимobserveфункция, ядро ​​состоит в том, чтобы вернутьObserverПример

return new Observer(value)

2. Инкапсуляция сообщений для реализации «транзитной станции».

Прежде всего, нам нужно понять, зачем нам инкапсулировать слой передачи сообщений?

Мы упомянули об этом, когда объясняли шаблон наблюдателя.适用性. То же самое и здесь: когда мы перехватываем изменение данных и уведомляем об изменении данных, если мы не делаем «транзитную станцию», мы не знаем, кто подписался на сообщение и сколько объектов подписалось на сообщение.

Это как агенты A (издатель) и B (подписчик) в истории, которую я упоминал выше. Агент А и Б передают информацию. Оба знают о существовании такого человека, как другой, но Агент А не знает, кто такой Б и сколько (подписчиков) на него подписано. Многие из них могут подписаться на агента А. информации, поэтому агент А (издатель) должен передать暗号Соберите всех (подписчиков), которые подписываются на его сообщения, здесь сбор подписчиков на самом деле является слоем封装. Затем шпион А просто публикует сообщение, а подписчики получают уведомление и просто делают своеupdateПросто сделай это.

Если быть проще, то шпион А, набравший подписчиков, просто публикует сообщение, а Б и больше просто подписываются на сообщение и делают соответствующиеupdateработы каждый модуль обеспечивает свою независимость, реализуя高内聚低耦合эти два принципа.

Без дальнейших церемоний, давайте поговорим о том, как vue выполняет инкапсуляцию сообщений.

2.1 Деп

Dep, полное название Зависимость, мы также можем примерно увидеть из названияDepКлассы используются для сбора зависимостей, как их собирать. Давайте посмотрим непосредственно на исходный код

let uid = 0

export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    // 用来给每个订阅者 Watcher 做唯一标识符,防止重复收集
    this.id = uid++
    // 定义subs数组,用来做依赖收集(收集所有的订阅者 Watcher)
    this.subs = []
  }

  // 收集订阅者
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null

Код короткий, но то, что он делает, важно

  1. Определите массив подписок для сбора подписчиков Watchers
  2. Когда изменения данных перехватываются, подписчик-наблюдатель уведомляется о необходимости выполнить операцию обновления.

В исходном коде также брошены два метода для работыDep.target, подробности следующим образом

// 定义收集目标栈
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  // 改变目标指向
  Dep.target = _target
}

export function popTarget () {
  // 删除当前目标,重算指向
  Dep.target = targetStack.pop()
}

2.2. Наблюдатель

WatcherИмеется в виду наблюдатель, за что он отвечает, так это за подпискуDep,когдаDepотправить сообщение прохождение (notify), так что подписывайтесьDepизWatchersсделает своеupdateработать. Ничего особенного, просто посмотрите исходный код, чтобы узнать.

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    this.cb = cb
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 解析表达式
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
      }
    }
    this.value = this.get()
  }

  get () {
    // 将目标收集到目标栈
    pushTarget(this)
    const vm = this.vm
    
    let value = this.getter.call(vm, vm)
    // 删除目标
    popTarget()
    
    return value
  }

  // 订阅 Dep,同时让 Dep 知道自己订阅着它
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 收集订阅者
        dep.addSub(this)
      }
    }
  }

  // 订阅者'消费'动作,当接收到变更时则会执行
  update () {
    this.run()
  }

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

В приведенном выше коде я удалил некоторые коды, не относящиеся к текущему обсуждению.Если вам нужно провести подробное исследование, вы можете проверить исходный код vue2.5.3 самостоятельно.

посмотри еще раз сейчасDepа такжеWatcher, нам нужно знать две точки

  1. DepОтвечает за сбор всех подписчиковWatcher, вам не нужно контролировать кого и сколько, вам просто нужно пройтиtargetУказывает на вычисление, чтобы собрать подписано на свои сообщенияWatcherВот и все, тогда просто хорошо поработайте над публикацией новостейnotifyВот и все.
  2. WatcherОтветственный за подпискуDep, а при подписке пустьDepсобирать, получатьDepПубликуя сообщение, делайте это хорошоupdateПросто сделай это.

Они кажутся зависимыми друг от друга, но на самом деле они обеспечивают их независимость и единство модулей.

больше приложений

vueЕсть также некоторые места, где используется «универсальный»观察者模式, такие как доставка событий между компонентами, с которыми мы знакомы,$onтак же как$emitдизайн.

$emitОтвечает за публикацию сообщений и общение с подписчиками$onУнифицированное потребление, то есть выполнениеcbsВсе события внутри.

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      this.$on(event[i], fn)
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn)
  }
  return vm
}

Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  let cbs = vm._events[event]
  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs
    const args = toArray(arguments, 1)
    for (let i = 0, l = cbs.length; i < l; i++) {
      cbs[i].apply(vm, args)
    }
  }
  return vm
}

Суммировать

В этой статье обсуждаются основные концепции, применимые сценарии и конкретные приложения шаблона наблюдателя в исходном коде vue. В этом разделе будут обобщены некоторые преимущества и недостатки шаблона Observer.

  1. Абстрактная связь между целью и наблюдателем: цель знает только, что у нее есть ряд наблюдателей (цель выполняет сбор зависимостей), но не знает, к какому конкретному классу принадлежит любой наблюдатель, поэтому связь между целью и наблюдателем является абстрактной и минимальный.
  2. Поддержка широковещательной связи: для связи в наблюдателе, в отличие от других распространенных запросов, необходимо указать получателя. Уведомление будет автоматически транслироваться на все связанные объекты, подписавшиеся на целевой объект, то есть вышеперечисленныеdep.notify(). Конечно, целевому объекту все равно, сколько объектов в нем заинтересовано. Его единственная обязанность — уведомить своих наблюдателей.
  3. Некоторые неожиданные обновления: поскольку сам наблюдатель не знает о существовании других наблюдателей, он может быть невежественным из окончательной стоимости изменения цели. Если наблюдатель работает непосредственно на цели, он может привести к серии обновлений наблюдателя и те объекты, которые зависят от этих наблюдателей, поэтому обычно мы приведем некоторые операции внутри цели, чтобы предотвратить вышеуказанные проблемы.

Хорошо, эта статья почти готова, и более подробные идеи дизайна исходного кода будут объяснены одна за другой в других статьях той же серии.

Лично я готов снова забрать свой официальный аккаунт.После этого гарантирую качественную и хорошую статью каждую неделю.Заинтересованные друзья могут обратить на нее внимание.