Тумблер интервью: Разговор о принципе вычисляемой реализации в Vue

внешний интерфейс исходный код JavaScript API Vue.js опрос внешний фреймворк
Тумблер интервью: Разговор о принципе вычисляемой реализации в Vue

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

Несмотря на то, что текущий стек технологий был перенесен из Vue в React, он по-прежнему очень приятен из фактического опыта нескольких проектов, разработанных с помощью Vue ранее.Документы Vue понятны и стандартизированы, дизайн API прост и эффективен, удобен для фронтенд-разработчиков. , и быстрый в использовании. Я даже думаю, что использование Vue более эффективно, чем React во многих сценариях. Раньше я периодически читал исходный код Vue, но я не разобрался в нем, поэтому я сделаю здесь некоторое техническое резюме и углублюсь мое понимание Vue, поэтому я напишу сегодня. является одним из наиболее часто используемых API в Vuecomputedпринцип реализации.

основное введение

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

<div id="app">
    <p>{{fullName}}</p>
</div>
new Vue({
    data: {
        firstName: 'Xiao',
        lastName: 'Ming'
    },
    computed: {
        fullName: function () {
            return this.firstName + ' ' + this.lastName
        }
    }
})

В Vue нам не нужно вычислять прямо в шаблоне{{this.firstName + ' ' + this.lastName}}, потому что слишком много декларативной логики в шаблоне сделает сам шаблон слишком тяжелым, особенно когда на странице используется большое количество сложных логических выражений для обработки данных, это сильно повлияет на удобство сопровождения страницы, иcomputedПервоначальный замысел дизайна также заключается в решении таких проблем.

Контрастный слушательwatch

Конечно, мы часто используемcomputedОн часто связан с другим API во Vue, который является слушателем.watchДля сравнения, поскольку в некотором смысле они одинаковы, они являются зависимыми механизмами отслеживания на основе Vue, когда зависимые данные изменяются, все соответствующие данные или полагаться на эту функцию будут автоматически изменять данные или вызовы.

Хотя вычисляемые свойства в большинстве случаев являются более подходящими, иногда также требуется пользовательский прослушиватель. Вот почему Vue проходитwatchОпции обеспечивают более общий подход к реагированию на изменения данных. Это наиболее полезно, когда вам нужно выполнять асинхронные или дорогостоящие операции при изменении данных.

Из официальных документов VuewatchИз объяснения мы можем узнать, что с помощьюwatchПараметры позволяют нам выполнять асинхронные операции (доступ к API) или дорогостоящие операции, ограничивать частоту выполнения операции и устанавливать промежуточное состояние до тех пор, пока мы не получим окончательный результат, что невозможно с вычисляемыми свойствами.

Ниже приведены некоторые дополнительные сведения оcomputedа такжеwatchРазница:

  1. computedзаключается в вычислении нового свойства и монтировании свойства в vm (экземпляр Vue) иwatchзаключается в том, что прослушиватель уже существует и подключен кvmданные, поэтому используйтеwatchтакже может контролироватьcomputedВычисленные изменения свойств (другиеdata,props)
  2. computedСуть в ленивом оцениваемом наблюдателе, с кешируемостью, только при изменении зависимости первый доступcomputedсвойство, будет вычислено новое значение, иwatchПри изменении данных будет вызвана функция исполнения
  3. Что касается сценариев использования,computedПрименяется, что на одни данные влияет несколько данных, иwatchПрименимо к данным влияния на несколько данных;

Выше мы понимаемcomputedа такжеwatchНекоторые различия между ними и различия в сценариях использования, конечно, иногда между ними есть не такие четкие и строгие ограничения.В конце концов, необходимо анализировать разные бизнесы.

Принципиальный анализ

Ближе к дому, вернемся к теме статьиcomputedЧтобы глубже понять внутренний механизм вычисляемых свойств, давайте шаг за шагом рассмотрим принцип их реализации в исходном коде Vue.

в анализеcomputedПеред исходным кодом мы должны иметь общее представление об адаптивной системе Vue. Vue называет ее ненавязчивой адаптивной системой. Модель данных — это обычные объекты JavaScript, и когда вы их изменяете, представление автоматически обновляется.

Когда вы передаете простой объект JavaScript экземпляру Vuedataвариант, Vue будет перебирать все свойства этого объекта и использоватьObject.definePropertyПреобразуйте все эти свойства вgetter/setter,Этиgetter/setterНевидимые для пользователя, но внутри они позволяют Vue отслеживать зависимости, уведомлять об изменениях при доступе к свойствам и их изменении, а каждый экземпляр компонента имеет соответствующийwatcherЭкземпляр объекта, который будет записывать свойства как зависимости во время рендеринга компонента, а затем использовать их как зависимостиsetterПри вызове он уведомитwatcherПересчитать, что приведет к обновлению связанных с ним компонентов.

Система ответов Vue имеет три основных момента:observe,watcher,dep:

  1. observe: траверсdataсвойства в , использованиеObject.definePropertyизget/setметод захвата данных;
  2. dep: у каждого свойства есть собственный подписчик сообщений.dep, используемый для хранения всех объектов-наблюдателей, подписанных на это свойство;
  3. watcher: Наблюдатель (объект), черезdepРеализуйте мониторинг атрибута ответа и, прослушав результат, активируйте собственный обратный вызов для ответа.

Теперь, когда у нас есть начальное представление о реактивных системах, давайте проанализируем вычисляемые свойства. Сначала мы находим, что инициализация вычисляемого свойства находится вsrc/core/instance/state.jsв файлеinitStateсделано в функции

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // computed初始化
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

называетсяinitComputedфункция (которая также инициализируется до и послеinitDataа такжеinitWatch) и передать два параметраvmэкземпляр иopt.computedопределено разработчикомcomputedварианты, перейдите кinitComputedфункция:

const computedWatcherOptions = { computed: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        'Getter is missing for computed property "${key}".',
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn('The computed property "${key}" is already defined in data.', vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn('The computed property "${key}" is already defined as a prop.', vm)
      }
    }
  }
}

Из этого кода давайте рассмотрим эти части:

  1. Получить определение вычисляемого свойстваuserDefа такжеgetterФункция оценки

    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    

    Есть два способа определить вычисляемое свойство: один — непосредственно следовать за функцией, другой — добавитьsetа такжеgetОбъектная форма метода, поэтому сначала получите определение вычисляемого свойстваuserDef, то согласноuserDefвведите, чтобы получить соответствующийgetterОценить функцию.

  2. Наблюдатели для вычисляемых свойствwatcherи подписчики сообщенийdep

    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )
    

    здесьwatchersто естьvm._computedWatchersСсылка на объект, содержащий наблюдателя для каждого вычисляемого свойства.watcherЭкземпляры (Примечание: «наблюдатели вычисляемых свойств», «подписчики» иwatcherОба относятся к одному и тому же значению, но обратите внимание, чтоWatcherотличие конструктора),WatcherКонструктор передал 4 параметра при создании экземпляра:vmпример,getterфункция оценки,noopпустая функция,computedWatcherOptionsпостоянный объект (предоставленный здесь дляWatcherличность{computed:true}элемент, указывающий, что это наблюдатель для вычисляемого свойства, а не для невычисляемого свойства, мы приходим кWatcherОпределение конструктора:

    class Watcher {
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
      ) {
        if (options) {
          this.computed = !!options.computed
        } 
    
        if (this.computed) {
          this.value = undefined
          this.dep = new Dep()
        } else {
          this.value = this.get()
        }
      }
      
      get () {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e) {
          
        } finally {
          popTarget()
        }
        return value
      }
      
      update () {
        if (this.computed) {
          if (this.dep.subs.length === 0) {
            this.dirty = true
          } else {
            this.getAndInvoke(() => {
              this.dep.notify()
            })
          }
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      evaluate () {
        if (this.dirty) {
          this.value = this.get()
          this.dirty = false
        }
        return this.value
      }
    
      depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
    }
    

    Для краткости и выразительности здесь я вручную удалил фрагменты кода, которые нам пока не нужны. НаблюдаемыйWatcherизconstructor, в сочетании с только что сказаннымnew WatcherЧетвертый параметр передается{computed:true}Да, для вычисляемых свойствwatcherбудет выполнятьifусловный кодthis.dep = new Dep(),а такжеdepТо есть подписчик сообщений, создавший это свойство.

    export default class Dep {
      static target: ?Watcher;
      subs: Array<Watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
    
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    
    Dep.target = null
      
    

    DepТакже упростим часть кода, наблюдаемWatcherа такжеDepотношения, выраженные в одном предложении

    watcherэкземпляр вdepи кdep.subsдобавил подписчиков вdepпройти черезnotifyпройденныйdep.subsуведомить каждогоwatcherвозобновить.

  3. defineComputedОпределение вычисляемых свойств

    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn('The computed property "${key}" is already defined in data.', vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn('The computed property "${key}" is already defined as a prop.', vm)
      }
    }
    

    потому чтоcomputedАтрибут монтируется непосредственно в экземпляр объекта, поэтому перед определением необходимо определить, есть ли переименование в объекте.defineComputedПередаются три параметра:vmэкземпляр, вычисляемое свойствоkeyтак же какuserDefОпределение вычисляемого свойства (объекта или функции). затем продолжайте искатьdefineComputedОпределение:

    export function defineComputed (
      target: any,
      key: string,
      userDef: Object | Function
    ) {
      const shouldCache = !isServerRendering()
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : userDef
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? shouldCache && userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set
          ? userDef.set
          : noop
      }
      if (process.env.NODE_ENV !== 'production' &&
          sharedPropertyDefinition.set === noop) {
        sharedPropertyDefinition.set = function () {
          warn(
            'Computed property "${key}" was assigned to but it has no setter.',
            this
          )
        }
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    

    В конце этого кода вызывается нативObject.definePropertyметод, где третий передаваемый параметр — это дескриптор свойстваsharedPropertyDefinition, инициализированный как:

    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    

    Тогда согласноObject.definePropertyПредыдущий код можно увидетьsharedPropertyDefinitionизget/setметод послеuserDefа такжеshouldCacheНапример, многократное переписывание после суждения, когда рендеринг не служебной стороны,sharedPropertyDefinitionизgetфункцияcreateComputedGetter(key)В результате находимcreateComputedGetterрезультат вызова функции и в конечном итоге перезаписатьsharedPropertyDefinitionПримерно представлено следующим образом:

    sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
                watcher.depend()
                return watcher.evaluate()
            }
        },
        set: userDef.set || noop
    }
    

    Выполняется при вызове вычисляемого свойстваgetФункции доступа для связывания объектов-наблюдателейwatcherзатем выполнитьwather.depend()Соберите зависимости иwatcher.evaluate()Вычислить оценку.

Проанализировав все шаги, давайте подытожим весь процесс:

  1. Когда компонент инициализируется,computedа такжеdataсоздадут свои собственные системы реагирования,ObserverтраверсdataКаждое свойство, установленное вget/setперехват данных
  2. инициализацияcomputedпозвонюinitComputedфункция
    1. Зарегистрироватьwatcherэкземпляр, и внутри этого экземпляраDepПодписчик сообщений используется для последующих зависимостей коллекции (таких как функция рендеринга).watcherили другие объекты, которые наблюдают за изменением вычисляемого свойстваwatcher)
    2. Когда вызывается вычисляемое свойство, егоObject.definePropertyизgetфункция доступа
    3. передачаwatcher.depend()метод своему собственному подписчику сообщенийdepизsubsдобавить другие свойстваwatcher
    4. передачаwatcherизevaluateметод (который, в свою очередь, вызываетwatcherизgetметод) сделать себя другимwatcherПодписчики сообщений подписчики, в первую очередьwatcherназначатьDep.target, затем выполнитеgetterФункция оценки при доступе к свойствам внутри функции оценки (например, изdata,propsили другиеcomputed), также вызовет ихgetфункция доступа, чтобы вычисляемое свойствоwatcherдобавлено к свойству в функции оценкиwatcherподписчик сообщенияdep, когда эти операции завершены, и, наконец, закрытьDep.targetназначить какnullи возвращает результат функции оценки.
  3. Запускается при изменении свойстваsetПерехватите функцию, затем вызовите ее собственного подписчика сообщенийdepизnotifyметод прохождения токаdepсохраняет всех подписчиков вwathcerизsubsмассив и вызывать один за другимwatcherизupdateспособ завершения обновления ответа.

текст / также

Кодировщик, жаждущий поэзии и дистанции

Монтаж/Флуоресценция

Автор разрешил опубликовать эту статью, и авторские права принадлежат Chuangyu Frontend. Пожалуйста, укажите источник для перепечатки этой статьи. Ссылка на эту статью:известно Sec-Fed.com/2018-09-12-…

Если вы хотите подписаться на другие сообщения с передовой линии KnownsecFED, выполните поиск и подпишитесь на нашу общедоступную учетную запись WeChat: KnownsecFED. Добро пожаловать, чтобы оставить сообщение для обсуждения, мы ответим как можно скорее.

Спасибо за прочтение.