Говоря о принципе Vue2

JavaScript Vue.js внешний фреймворк MVVM

Эта статья посвящена общему процессу рендеринга Vue2, включая реализацию ответа данных (двустороннее связывание), компиляцию шаблонов, принципы виртуального дома и т. д. Я надеюсь, что читатели что-то поймут после ее прочтения.

Исходная ссылка синхронизации блога:IM и JM.com/article/59 не…

предисловие

Эта часть контента сначала знакомит с некоторыми функциями основного фреймворка переднего плана, чтобы улучшить общее понимание фреймворка, и, наконец, дает общее введение в принцип vue2.
Обратитесь к You Yuxi в прямом эфиреПоговорим о фронтенд-фреймворках
Заинтересованные студенты могут прослушать

Современные основные фреймворки используют数据=>视图образом, скрывает утомительную операцию dom, принимает декларативное программирование (Declarative Programming) заменяет jquery-подобное императивное программирование прошлого (Imperative Programming)

$("#xxx").text("xxx");
// 变为下者
view = render(state);

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

Чтобы достичь этого, необходимо понять, как вводить данные и выводить представления.Мы заметим вышеупомянутую функцию рендеринга.Реализация функции рендеринга в основном заключается в оптимизации производительности DOM.Конечно, существуют различные методы реализации, такие как direct innerHTML, Using documentFragment и virtual dom имеют разную производительность в разных сценариях, но фреймворк преследует то, что фреймворк уже удовлетворил ваши потребности в оптимизации в большинстве сценариев Мы не будем здесь вдаваться в подробности, которые будут упомянуты позже.

Конечно, есть также обнаружение изменения данных, поэтому повторный рендеринг, обнаружение изменения данных, стоит упомянуть, что производитель данных (Producer) и потребители данных (Consumer), здесь мы можем временно рассматривать систему (представление) как потребителя данных, наш код устанавливает изменения данных и действует как производитель данных
Мы можем разделить здесь系统不可感知数据变化и系统可感知数据变化

В Rx.js связь между ними делится на pull (Pull) и нажмите (Push), понять не просто, тут я сам разделил класс

  • Система не может воспринимать изменения данных

Фреймворки, такие как React/Angular, не знают, когда данные изменились, но когда обновляется их представление. Например, React отправляет сигнал системе через setState, чтобы сообщить системе, что данные могли измениться, а затем отображает представление. через виртуальный дом diff.Происходит процесс проверки грязных значений, проходя через сравнение

  • Система может обнаруживать изменения данных

Rx.js/vue такой отзывчивый, через режим наблюдателя, используйте Observable (наблюдаемый объект), Observer (наблюдатель) (или наблюдатель) для подписки (например, рендеринг представления, на самом деле, также может использоваться как наблюдатель подписывается на данные, которые будут упомянуты позже), система может точно знать, где данные изменились, чтобы можно было обновить и отобразить представление.

начальство系统不可感知数据变化, крупнозернистая, а иногда и ручная оптимизация (например, pureComponent и shouldComponentUpdate) требуется для пропуска некоторых представлений, данные которых не будут обновляться для повышения производительности.
ниже系统可感知数据变化, мелкозернистый, но связывающий большое количество наблюдателей, возникает много накладных расходов памяти на отслеживание зависимостей

так

Вот, наконец, главный герой этой статьиVue2, он использует компромиссный метод детализации, детализация находится на уровне компонентов, и наблюдатель подписывается на данные.Когда данные изменяются, мы можем узнать, какие данные компонента изменились, а затем использовать виртуальный dom diff для обновления соответствующего компонента.

Позже мы также расширим, как он реализует эти процессы, мы можем начать с простого приложения.

из простого приложения

<div id="app">
  {{ message }}
</div>

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
app.message = `xxx`; // 发现视图发生了变化

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

  • ответ данных? Как узнать об изменении данных?

    Так же есть небольшая деталь, как app.message получает сообщение в vue data?

  • Как изменения данных связаны с представлениями?
  • Что такое виртуальный дом? Что такое виртуальный дом?

Конечно, мы также объясним некоторые связанные понятия, такие как сбор зависимостей.

Принцип ответа данных

Object.defineProperty

Ядром ответа данных Vue является использованиеObject.definePropertyМетод (IE9+) определяет или изменяет свойства объекта Ключом для доступа к дескрипторам являются get и set, которые предоставляются методам получения и установки свойств.

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

var obj = {};
Object.defineProperty(obj, 'msg', {
  get () {
    console.log('get')
  },
  set (newValue) {
    console.log('set', newValue)
  }
});
obj.msg // get
obj.msg = 'hello world' // set hello world

Кстати, эта маленькая деталь

Как app.message получает сообщение в данных vue?

На самом деле это такжеObject.definePropertyСвязанный
Vue будет проходить через прокси данных при инициализации данных

function initData (vm) {
    let data = vm.$options.data
    vm._data = data
    const keys = Object.keys(data)
    let i = keys.length
    while (i--) {
        const key = keys[i]
        proxy(vm, `_data`, key)
    }
    observe(data)
}

Что делает прокси?

function proxy (target, sourceKey, key) {
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: true,
      get () {
        return this[sourceKey][key]
      }
      set () {
        this[sourceKey][key] = val
      }
    })
}

На самом деле, используйтеObject.definePropertyДополнительный уровень доступа
Таким образом, мы можем использоватьapp.messageдоступ кapp.data.message
также считатьObject.definePropertyмаленькое приложение

После разговора о базовом уровне этой грамматики я узнал, как узнать, что данные изменились, но ответ на ответ все еще есть.Далее поговорим о том, как Vue реализует ответ данных?
На самом деле, это решение следующих проблем: как реализовать $watch?

const vm = new Vue({
  data:{
    msg: 1,
  }
})
vm.$watch("msg", () => console.log("msg变了"));
vm.msg = 2; //输出「msg变了」

Шаблон наблюдателя (наблюдатель, наблюдатель, деп)

В Vue есть три важных класса для реализации отзывчивости: класс Observer, класс Watcher, класс Dep.
Здесь я дам общее введение (подробности см. в англоязычной аннотации к исходному коду)

  • Класс Observer в основном используется для передачи данных в Vue.definePropertyДобавляйте методы getter/setter и собирайте зависимости или уведомляйте об обновлениях в getters/setters
  • Класс Watcher используется для наблюдения за изменениями данных (или выражений), а затем выполняет функцию обратного вызова (которая также имеет процесс сбора зависимостей), в основном используемую для $watch API и инструкций.
  • Класс Dep — это наблюдаемый объект, на который можно подписаться с помощью различных инструкций (это многоадресная рассылка).

Шаблон наблюдателя, аналогичный шаблону публикации/подписки.
Но на самом деле это немного другое.Режим публикации/подписки основан на едином центре распределения и планирования событий.On добавляет события (подписки) в массив в центре, а emit берет события (опубликованные) из массива в центр, публикует и подписывается, а также планирует после публикации. Все операции с подписчиками выполняются центром

Однако в режиме наблюдателя такого центра нет.Наблюдатель подписывается на наблюдаемый объект.Когда наблюдаемый объект публикует событие, поведение наблюдателя отправляется напрямую, поэтому здесь наблюдатель и наблюдаемый объект фактически имеют отношение зависимости ., что не отражено в модели публикации/подписки.

На самом деле, Dep — это аббревиатура зависимости.

Как реализовать шаблон наблюдателя?

Давайте сначала посмотрим на следующий код. Следующий код реализует процесс подписки Watcher на Dep. Поскольку Dep может быть подписан несколькими Watcher, у него есть массив подписчиков. После подписки просто поместите Watcher в массив.

class Dep {
  constructor () {
    this.subs = []
  }
  notify () {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
        subs[i].update()
    }
  }
  addSub (sub) {
    this.subs.push(sub)
  }
}

class Watcher {
  constructor () {
  }
  update () {
  }
}

let dep = new Dep()
dep.addSub(new Watcher()) // Watcher订阅了依赖

Мы реализовали подписку, а как быть с публикацией уведомлений, то есть где вышеописанное уведомление реализовано?

Когда мы доберемся сюда, мы можем связаться с ответом данных. Что нам нужно, так это изменение данных, чтобы уведомить об обновлении, которое, очевидно, будет реализовано в сеттере в defineProperty. Вы должны думать об этом с умом, мы можем рассматривать каждые данные как Dep Экземпляр, а затем перейдите к уведомлению, когда установщик включен, поэтому мы можем создать новый Dep() в defineProperty, и мы можем получить экземпляр Dep через установщик закрытия

как ниже

function defineReactive (obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
            //...
        },
        set: function reactiveSetter (newVal) {
            //...
            dep.notify()
        }
    })
}

Тогда вот еще одна проблема
Вы поместили в него экземпляр Dep. Как я могу подписать свой экземпляр Watcher на этот экземпляр Dep? Vue реализовал здесь тонкое прикосновение. Можно получить этот экземпляр Dep из get. , так что вы можете получить значение и вызвать геттер для сбора зависимостей при выполнении операции наблюдения.

function defineReactive (obj, key, val) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)

    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() // 等价执行dep.addSub(Dep.target),在这里收集
            }
            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            if (newVal === value) {
                return
            }
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            dep.notify()
        }
    })

Здесь мы также должны посмотреть на реализацию Watcher

class Watcher () {
  constructor (vm, expOrFn, cb, options) {
    this.cb = cb
    this.value = this.get()
  }
  get () {
    pushTarget(this) // 标记全局变量Dep.target
    let value = this.getter.call(vm, vm) // 触发getter
    if (this.deep) {
      traverse(value)
    }
    popTarget() // 标记全局变量Dep.target
    return value
  }
  update () {
    this.run()
  }
  run () {
      const value = this.get() // new Value
      // re-collect dep
      if (value !== this.value ||
          isObject(value)) {
          const oldValue = this.value
          this.value = value
          this.cb.call(this.vm, value, oldValue)
      }
  }
}

Таким образом, мы выполним операцию оценки, когда станем новым наблюдателем, а затем, поскольку наблюдатель помечен как запущенный, зависимости будут собраны, то есть наблюдатель подпишется на зависимости (эта оценка может вызвать более одного получателя, это может быть Срабатывает много геттеров, затем собирается несколько зависимостей), мы можем обратить внимание на приведенную выше операцию запуска, то есть операцию, выполняемую наблюдателем после dep.notify(), и появится операция получения, мы можем заметить Вот новая волна зависимостей! (Конечно, есть связанные операции дедупликации)

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

const vm = new Vue({
  data: {
    msg: 1,
  }
})
vm.$watch("msg", () => console.log("msg变了"));
vm.msg = 2; //输出「变了」

$watcher на самом деле является пакетом нового Watcher
То есть новый Watcher(vm, 'msg', () => console.log("msg изменено"))

  • Во-первых, новый Vue просматривает данные и добавляет методы получения/установки к данным defineProperty.
  • мыnew Watcher(vm, 'msg', () => console.log("msg变了")), Сначала отметьте глобальную переменную Dep.target = экземпляр Watcher, затем выполните операцию get msg, активируйте его геттер, затем dep успешно получает своих подписчиков, помещает их в свой массив подписчиков, и, наконец, мы dep.target = null
  • Наконец, установите vm.msg = 2, активируйте установщик, dep.notify в замыкании, просмотрите массив подписчиков и выполните соответствующую операцию обратного вызова.

На самом деле, когда дело доходит до этого, основной принцип реагирования почти такой же.

Но на самом деле Object.defineProperty не панацея,

  • Операции push/pop массива
  • Не могу отследить изменение длины массива
  • Массив arr[xxx] = yyy не может быть обнаружен
  • Точно так же незаметно добавление и удаление свойств объекта.

Для решения этих проблем собственных ограничений js

  • Vue сначала изменяет метод массива, используя__proto__Наследуйте эти методы (если нет, непосредственно defineProperty в массив один за другим), конкретный метод мутации должен добавить операцию dep.notify позади
  • Что касается добавления и удаления свойств, то можно представить, что если мы добавим свойства, то у нас вообще не будет defineProperty, а если мы удалим свойства, то даже наше предыдущее defineProperty будет удалено, поэтому здесь Vue добавляет API $set/$delete для достижения этих целей операция также является операцией добавления dep.notify в конце
  • Конечно, вышесказанное не достигается простым использованием отложений, соответствующих всем данным в defineProperty.Также есть экземпляр отложения в классе Observer, и экземпляр отложения будет присоединен к данным.__ob__атрибут для получения своего экземпляра Observer, например, вышеупомянутые специальные операции с массивами и объектами, когда часы собирают зависимости, зависимости будут собраны, а затем dep, наконец, используется для уведомления об обновлении

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

Здесь мы можем кратко упомянуть новую функцию ES6, Proxy, которая, вероятно, станет главным героем механизма ответа следующего поколения, потому что она может решить наши вышеупомянутые недостатки, но ее нельзя использовать должным образом из-за проблем совместимости, поэтому давайте жду с нетерпением~

Теперь давайте посмотрим на эту картинку с официального сайта Vue.


По крайней мере, теперь у нас есть четкая картина правой половины, ясно, как данные взаимодействуют с Watcher, но функция рендеринга, Watcher, как запускать функцию рендеринга, все еще требуют ответа, и, конечно же, виртуальное дерево DOM в левом нижнем углу.

Как связаны данные и представления

Я извлек ключевой фрагмент кода Vue здесь

class Watcher () {
  constructor (vm, expOrFn, cb, options) {
  }
}
updateComponent = () => {
   // hydrating有关ssr本文不涉及
    vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
// noop是回调函数,它是空函数

На самом деле это основные отношения между Watcher и Render.

Помните, что мы сказали выше, при выполнении нового Watcher будет операция оценки.Оценка здесь является функциональным выражением, то есть выполняется updateComponent.После выполнения updateComponent он будет выполняться снова.vm._render(), передать параметры вvm._update(vm._render(), hydrating), он заканчивается после того, как зависимости собраны.Здесь есть два ключевых момента.vm._renderделать что?vm._updateделать что?

vm._render

ПосмотримVue.prototype._renderГде это святое (далее код нужно удалить)

  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const {
      render,
      staticRenderFns,
      _parentVnode
    } = vm.$options
    // ...
    let vnode
    try {
      // vm._renderProxy我们直接当成vm,其实就是为了开发环境报warning用的
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {

    }

    // set parent
    vnode.parent = _parentVnode
    return vnode
  }

Итак, здесь мы видим, что функция рендеринга выполняется, функция рендеринга исходит из опций, а затем возвращает vnode.

Итак, здесь мы можем обратить внимание на то, откуда взялась эта функция рендеринга.

Если вы знакомы с Vue2, вы, возможно, знаете, что Vue предоставляет опцию, которая использует рендеринг в качестве этой функции.Что, если эта опция не предусмотрена?
Давайте посмотрим на жизненный цикл


Мы видим, чтоCompile template into render function(Без шаблона externalHTML el будет считаться шаблоном), поэтому здесь идет процесс компиляции шаблона

Компиляция шаблона

Возьмите еще один кусок основного кода

const ast = parse(template.trim(), options) // 构建抽象语法树
optimize(ast, options) // 优化
const code = generate(ast, options) // 生成代码
return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
}

Мы видим, что вышеизложенное разделено на три части.

  • Преобразование шаблона в абстрактное синтаксическое дерево
  • Оптимизация абстрактных синтаксических деревьев
  • Генерация кода из абстрактных синтаксических деревьев

Что именно там было сделано? Здесь я кратко расскажу о

  • Первая часть на самом деле представляет собой множество закономерностей.Для сопоставления левых и правых открытых и закрытых тегов и набора атрибутов через форму стека стек постоянно извлекается в стек для сопоставления и замены родительского узла, и наконец, создается объект, включая дочерние элементы, дочерние элементы и дочерние элементы.
  • Вторая часть основана на первой части, находит некоторые статические узлы в соответствии с типом узла и помечает их.
  • Третья часть — генерация кода функции рендеринга.

Так что в конце концов это будет иметь такой эффект

шаблон

<div id="container">
  <p>Message is: {{ message }}</p>
</div>

Создать функцию рендеринга

(function() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "container"
            }
        }, [_c('p', [_v("Message is: " + _s(message))])])
    }
}
)

Здесь мы можем снова объединить приведенный выше код

 vnode = render.call(vm._renderProxy, vm.$createElement)

в_cэтоvm.$createElement

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

vm.$createElement на самом деле является API для создания vnodes.

понялvm._render()Создал возврат vnode, следующий шагvm._updateохватывать

vm._update

vm._updateЧасть этого также связана с виртуальным домом. В следующем разделе он будет представлен подробно. Сначала мы можем раскрыть функцию функции. Как следует из названия, это обновление представления и обновление его до представления в соответствии с входящим внод.

Общий поток данных для представлений

Итак, здесь мы можем сделать вывод об общем процессе просмотра данных.

  • На уровне компонентов vue выполнит новый Watcher.
  • Новый Watcher сначала будет иметь операцию оценки, ее оценка заключается в выполнении функции, эта функция будет выполнять рендеринг, который может иметь операцию компиляции шаблона в функцию рендеринга, затем генерировать vnode (виртуальный дом), а затем преобразовать виртуальный дом, применить к представлению
  • Среди них, когда виртуальный дом применяется к представлению (diff будет обсуждаться позже), выражение в нем должно быть оценено (например, {{message}}, мы обязательно получим его значение и отрендерим), это вызовет соответствующая операция геттера для завершения сбора зависимостей
  • Когда данные изменяются, он уведомляет Watcher на уровне компонентов, а затем оценивает повторный сбор зависимостей и повторную визуализацию представления.

Давайте еще раз взглянем на эту картинку на официальном сайте Vue.


Все прошло гладко!

Virtual DOM

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

Почему существует виртуальный DOM?

Друзья, которые занимались оптимизацией производительности внешнего интерфейса, должны знать, что операции DOM очень медленные, и нам нужно сократить их операции.
Почему это медленно?
Мы можем попробовать ввести слой ключей DOM


Мы видим, что его свойства огромны, не говоря уже о том, что это всего лишь один слой.

В то же время, когда вы работаете непосредственно с DOM, вы должны обратить внимание на некоторые операции, которые могут вызвать перестановку.

Итак, какова роль Virtual DOM? На самом деле это слой буфера из нашего кода для работы с DOM.Поскольку работа с DOM медленная, позвольте мне быстро работать с объектом js, я буду работать с объектом js, а затем, наконец, преобразую объект в настоящий DOM.

Таким образом, это становится кодом => Виртуальный DOM (специальный объект js) => DOM

Что такое виртуальный DOM

По сути, мы ответили выше, что такое виртуальный DOM, это специальный объект js
Мы можем видеть, как определяется Vnode во Vue?

export class VNode {
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.functionalContext = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
}

Использование вышеуказанных атрибутов может представлять узел DOM.

Алгоритм виртуального DOM

Здесь речь о вышеизложенномvm.updateоперация

  • Первое — это дерево описания js-объекта (Virtual DOM) (vm._render), преобразовать вставку dom (первый рендеринг)
  • Изменения состояния, создание нового js-объекта (Virtual DOM), сравнение старого и нового объектов
  • Примените изменения к DOM и сохраните новый объект js (Virtual DOM), повторите второй шаг.

Используйте js-объекты для описания дерева (сгенерируйте Virtual DOM).В Vue оно сначала преобразуется в AST для генерации кода, а затем $creatElement используется для генерации Virtual DOM в виде Vnode (vm._render的操作)

Здесь мы можем подробно рассмотретьvm._update(На самом деле это два последних шага алгоритма Virtual DOM)

  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    // ...
    if (!prevVnode) {
      // initial render
      // 第一次渲染
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
    } else {
      // updates
      // 更新视图
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // ...
  }

Ключевой момент можно увидетьvm.__patch__, по сути, это ядро ​​Virtual DOM Diff, а также последнее место, куда вставляется настоящий DOM.

Virtual DOM Diff

Полный алгоритм Virtual DOM Diff, согласно статье (я забыл, где), требует O (n ^ 3), поскольку он включает повторное использование на разных уровнях, на этот раз сложность неприемлема, принимая во внимание, что DOM меньше участвует в повторном использовании между уровнями. , поэтому он сводится к текущему уровню повторного использования, а сложность этого алгоритма снижается до O(n), Perfect~

Обратитесь к классической диаграмме React, чтобы помочь всем понять ее.Один и тот же цветной круг слева и справа - это область сравнения/повторного использования.

Переходим к теме, давайте взглянем на функцию patch в Vue.

function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      // 老节点不存在,直接创建元素
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        // 新节点和老节点相同,则给老节点打补丁
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        // ... 省略ssr代码
        // replacing existing element
        // 新节点和老节点相同,直接替换老节点
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )
      }
    }
    // ...省略代码
    return vnode.elm
  }

Итак, патч, вероятно, делает следующие вещи

  • Определить, существует ли старый узел или нет
    • Если он не существует, это первый рендеринг, и элемент создается напрямую.
    • Если он существует, тот же Vnode используется для оценки того, являются ли корневые узлы одинаковыми.
      • Если то же самое, используйте patchVnode для исправления старого узла.
      • Если нет, используйте новый узел, чтобы напрямую заменить старый узел.

Для суждения о том же самом Vузле это на самом деле простое сравнение нескольких суждений об атрибутах.

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

для patchVnode
Фактически, это сравнение дочерних узлов узла и оценка дочерних узлов, принадлежащих старому и новому узлам соответственно.Если ни один из двух или один из них не существует, проще удалить или добавить напрямую, но если оба имеют подузлы, которые включают сравнение списков и некоторые операции мультиплексирования, реализованные updateChildren

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    if (oldVnode === vnode) {
      // 新老节点相同
      return
    }
    // ... 省略代码
    if (isUndef(vnode.text)) {
      // 假如新节点没有text
      if (isDef(oldCh) && isDef(ch)) {
        // 假如老节点和新节点都有子节点
        // 不相等则更新子节点
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        // 新节点有子节点,老节点没有
                // 老节点加上
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // 老节点有子节点,新节点没有
                // 老节点移除
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        // 老节点有文本,新节点没有文本
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      // 假如新节点和老节点text不相等
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

Давайте взглянем на это обновление Дети в конце
Эта часть на самом деле.com/problems/quota… Ли ТШОДля проблемы минимального расстояния редактирования не существует сложного алгоритма динамического программирования (сложность O (m * n)) для достижения наименьшей операции движения, но вы можете пожертвовать определенной операцией dom для оптимизации некоторых сцен, сложность может быть уменьшена до O(max(m, n), сравните головной и хвостовой узлы соответственно, если нет совпадения, используйте ключ первого узла (вот то, что мы часто используем в v-for), чтобы найти тот же ключ для сравнения патчей, если Если ключа нет, он будет напрямую проходить, чтобы найти похожие узлы, если есть исправление, если нет, создаст новый узел.

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

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

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        // 假如老节点的第一个子节点不存在
        // 老节点头指针就往下一个移动
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        // 假如老节点的最后一个子节点不存在
        // 老节点尾指针就往上一个移动
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // 假如新节点的第一个和老节点的第一个相同
        // patch该节点并且新老节点头指针分别往下一个移动
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // 假如新节点的最后一个和老节点的最后一个相同
        // patch该节点并且新老节点尾指针分别往上一个移动
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // 假如新节点的最后一个和老节点的第一个相同
        // patch该节点并且新节点尾指针往上一个移动,老节点头指针往下一个移动
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // 假如新节点的第一个和老节点的最后一个相同
        // patch该节点并且老节点尾指针往上一个移动,新节点头指针往下一个移动
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        // 创建老节点key to index的映射
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key] // 假如新节点第一个有key,找该key下老节点的index
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 假如新节点没有key,直接遍历找相同的index
        if (isUndef(idxInOld)) { // New element
          // 假如没有找到index,则创建节点
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
        } else {
          // 假如有index,则找出这个需要move的老节点
          vnodeToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // move老节点和新节点的第一个基本相同则开始patch
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
            // 设置老节点空
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // 不同则还是创建新节点
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) {
      // 假如老节点的头指针超过了尾部的指针
      // 说明缺少了节点
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      // 假如新节点的头指针超过了尾部的指针
      // 说明多了节点
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }

Суммировать

На этом этапе объясняется общий принцип Vue2.Есть еще много деталей, которые не были подробно рассмотрены.Читатели могут прочитать исходный код для углубленного изучения.
Мы можем просмотреть вопрос в начале (на самом деле в тексте постоянно задаются вопросы для решения проблем), как вы видите здесь, я надеюсь, что вы сможете что-то получить ~

  • ответ данных? Как узнать об изменении данных? (подсказка: определить свойство)

    Так же есть небольшая деталь, как app.message получает сообщение в vue data?

  • Как изменения данных связаны с представлениями? (Подсказка: Наблюдатель, Деп, Наблюдатель)
  • Что такое виртуальный дом? Что такое виртуальный дом? (подсказка: специальный объект js)

Справочные ссылки / рекомендуемое чтение

Наконец

Спасибо за чтение~
Добро пожаловать, чтобы следовать за мной, ха-хаgithub.com/BUPT-HJM
Добро пожаловать, чтобы продолжить осмотр достопримечательностей моего нового блога~

Добро пожаловать, чтобы следовать

Категории