Vue понимает народный геттер/сеттер

Vue.js

оригинальныйлучший опыт чтения

Когда вы передаете простой объект JavaScript экземпляру Vuedataвариант, Vue будет перебирать все свойства этого объекта и использоватьObject.definePropertyПреобразуйте все эти свойства вgetter/setter. Object.defineProperty — это функция, которую нельзя изменить в ES5, поэтому Vue не поддерживает браузеры IE8 и более ранних версий.

Взято из вышеперечисленногоУглубленные принципы реагирования

Итак, каков конкретный процесс преобразования всех этих свойств в геттеры/сеттеры? Эта статья не является углубленной и конкретной, но кратко описывает его процесс, стремясь понять его в целом и понять его основные идеи.

Предположим, что код выглядит следующим образом:

const vm = new Vue({
  el: '#app',
  data: {
    msg: 'hello world'
  }
})

dataОпция может получить объект или метод, вот пример объекта (на самом деле он будет преобразован в объект в конце)

Во-первых, все пары ключ-значение этого объекта будут смонтированы вvm._dataна (дополнительноvm._dataСуществует также__ob__ключ, который пока можно игнорировать), так что мы можем использоватьvm._data.msgдоступ к данным

Но обычно мы используемvm.msgКак получить доступ к данным таким образом? По сути, это прокси для доступа vm[key] в паре ключ-значение данных к vm._data[key]

proxy(vm, `_data`, key)

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

как правилоvm._data(переменная подчеркивания) используется как внутренняя программа, а открытый APIvm.$data, по сути эти два тоже одно и то же, и тоже сделал прокси, код наверное такой:

const dataDef = {}
dataDef.get = function () { return this._data }

Object.defineProperty(Vue.prototype, '$data', dataDef)

if (process.env.NODE_ENV !== 'production') {
  dataDef.set = function () {
    warn(
      'Avoid replacing instance root $data. ' +
      'Use nested data properties instead.',
      this
    )
  }
}

Простое понимание состоит в том, чтобы получить доступ к vm.data 的时候,就会代理到 vm.\_data,所以访问 vm.data.msg фактически обращается к vm._data.msg. Если непосредственно в среде разработки на вм.data 赋值会有个警告(是 `vm.data = xxx这样的赋值,而不是vm.$data.msg = xxx`, последнее нормально)

На данный момент мы понимаем, почему мы можем использоватьvm.msg,vm._data.msgтак же какvm.$data.msgЕсть три способа получить/изменить данные, самые примитивные данныеvm._data.msg, а два других представляют данные _data,vm.$data.msgЭто API, предоставляемый Vue, как правило, мы используем его непосредственно для разработки.vm.msgЭто все более и более удобно.Если вы хотите получить все данные, вам нужно использоватьthis.$data, вместоthis.data

Далее поговорим о геттере/сеттере

Добавьте немного кое-что к демонстрации:

const vm = new Vue({
  el: '#app',
  data: {
    msg: 'hello world'
  },
  computed: {
    msg2() {
      return this.msg + '123'
    }
  }
})

msg2 зависит от msg. При изменении msg значение msg2 должно обновляться автоматически. Изменение msg можно сделать вvm._data.msgСлушатель в сеттере,Но откуда вы знаете, что msg2 зависит от msg?

Интуитивно мы можем думать о обходе пар ключ-значение всех вычисляемых объектов и последующем их анализе.Это кажется выполнимым в теории, но я думаю, что это может потребовать разбора AST или регулярного сопоставления, чтобы увидеть, используется ли он.this.msg, или возможноthis.$data.msgах, может бытьthis._data.msg, а также траверсdataВсе ключи в , это кажется слишком хлопотным, и,Если в программе не используется msg2, то он не нужен?

На самом деле, когда Vue инициализируется, онvm._dataУстановите геттер/сеттер для каждой пары ключ-значение, приблизительный код выглядит следующим образом:

// obj 即为 vm._data
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
  defineReactive(obj, keys[i])
}

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
    /* eslint-disable no-self-compare */
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    /* eslint-enable no-self-compare */
    if (process.env.NODE_ENV !== 'production' && customSetter) {
      customSetter()
    }
    // #7981: for accessor properties without setter
    if (getter && !setter) return
    if (setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    childOb = !shallow && observe(newVal)
    dep.notify()
  }
})

Суть отзывчивости Vue заключается в том, что геттер собирает зависимости, а сеттер инициирует обновление зависимостей.

Давайте по-прежнему возьмем в качестве примера выше вычисленный msg2.Когда мы переходим к значению msg2 в первый раз (обратите внимание, что это должно быть поведение получения значения, оно может быть в шаблоне или программе), необходимо принять значение.this.msg, который вызовет геттер msg, и в этот момент мы сможем определить, что msg2 зависит от msg

От чего может зависеть msg? Кажется, есть три

  1. шаблон в шаблоне
  2. computed
  3. watch

мы можем напечататьvm._watchersПредставление представляет собой массив экземпляров Watcher, непосредственно смотрите на значение выражения экземпляра,На самом деле, когда это выражение срабатывает, срабатывает геттер msg.

И это выражение соответствует трем вышеперечисленным ситуациям, потому что при изменении msg эти выражения нужно пересчитывать, поэтому эти зависимости нужно сохранять, поэтому в исходном коде задан этот класс Watcher

A watcher parses an expression, collects dependencies, and fires callback when the expression value changes. This is used for both the $watch() api and directives.

Массив watcher.deps представляет зависимости наблюдателя, а значением является экземпляр Dep, который можно понимать как данные данных, связанные с выражением экземпляра наблюдателя. Обратите внимание, что массив deps может быть пустым, для шаблона может быть так, что шаблон не зависит от данных, а для вычисляемого может быть так, что вычисляемые данные не были получены (например, я определил msg2, но это не так). используется в программе Это Когда deps пусто, это означает, что если я меняю msg, мне не нужно оповещать msg2, потому что msg2 вообще не используется, но я ввожу в консоль vm.msg2, что вызывает getter msg, а затем выполняет сбор зависимостей. , то deps больше не пуст, что указывает на то, что я использовал msg2, и мне нужно уведомить msg2, чтобы внести изменения при следующем обновлении msg)

Что касается часов, я ни при каких обстоятельствах не пробовал deps, и он не пустой, что требует дополнительной проверки исходного кода для подтверждения.

Элемент массива deps — это экземпляр Dep, который имеет атрибут subs, представляющий собой массив экземпляров Watcher, представляющих элементы, зависящие от этого Dep.

Watcher и Dep трудно понять, но пока можно понять: Dep и данные связаны, каждый экземпляр Dep соответствует паре данных ключ-значение, а экземпляр Watcher зависит от Dep, поэтому есть три ситуации, которые будет зависеть, то есть от вышеперечисленных трех (Подумайте об этом, когда данные обновляются, только ли эти три места нужно обновлять одновременно, или отвечать одновременно)

Подводя итог: будемdataВсе пары ключ-значение в наборе геттер/сеттер, когда геттер, мы будем собирать зависимости (зависимости - это три вышеперечисленных элемента, не все зависимости будут собраны в любом случае, например печать msg в хуке, зависимости в на этот раз, так что в исходном коде здесь есть сложные суждения), когда сеттер установлен, мы будем запускать собранные зависимости для обновления данных.После их понимания мы можем изначально понять принцип реагирования Vue.

Если вы видите конец, вы можете обратить внимание на мой публичный аккаунт "Code Farmer's Notes"