предисловие
Недавно я изучал исходный код вычисляемых свойств Vue и обнаружил, что есть некоторые отличия от внутренней реализации обычных реактивных переменных.Я специально написал этот блог, чтобы зафиксировать результаты своего обучения.
Скриншоты исходного кода в этой статье сохраняют только основную логику.Полный исходный адрес
Вам может понадобиться понять некоторые принципы реактивного Vue.
vue-версия:2.5.21
Концепция вычисляемых свойств
Общее вычисляемое значение свойства — это функция, которая возвращает значение и может зависеть от других переменных внутри функции.
Общие вычисляемые свойства похожи на методы, а значение — это функция, так в чем между ними разница?
Разница между вычисляемым свойством и методом
Помещение функции вычисляемого свойства в методы также может дать тот же эффект.
Но если представление зависит от возвращаемого значения этого метода, и при обновлении представления из-за модификации другой реактивной переменной метод будет выполнен снова, даже если это обновление не имеет ничего общего с зависимыми переменными в методе !
А для вычисляемых свойств, только если вычисляемое свойствополагатьсяПосле изменения переменной функция будет выполнена снова и будет возвращено новое значение
Когда переменная otherProp изменяется для обновления представления, methodFullName будет выполняться каждый раз, в то время как calculatedFullName будет выполняться только один раз при инициализации страницы.Vue рекомендует разработчикам разделять методы и вычислительные свойства, что может эффективно повысить производительность и избежать выполнения. , какой-то ненужный код
Изучив концепцию вычисляемых свойств, давайте углубимся в исходный код, чтобы понять, как реализованы вычисляемые свойства и почему они переоцениваются только при изменении зависимостей вычисляемых свойств.
Начните с примера
Здесь я написал простой пример, чтобы помочь вам понять принцип работы вычисляемых свойств. Следующий анализ будет сосредоточен на этом примере.
const App = {
template: `
<div id="app">
<div>{{fullName}}</div>
<button @click="handleChangeName">修改lastName</button>
</div>
`,
data() {
return {
firstName: '尤',
lastName: '雨溪',
}
},
methods: {
handleChangeName() {
this.lastName = '大大'
}
},
computed: {
fullName() {
return this.firstName + this.lastName
}
}
}
new Vue({
el: '#app',
components: {
App
},
template: `
<App></App>
`
}).$mount()
fullName зависит от firstName и lastName.Нажатие на кнопку изменит lastName, при этом будет пересчитан fullName и вид станет "Особенно большим"
Углубить исходный код атрибутов
Вычисляемые свойства, написанные в ежедневной разработке, фактически сохраняют наблюдателя внутри. Функция наблюдателя состоит в том, чтобы наблюдать за изменением отзывчивой переменной, а затем выполнять соответствующий обратный вызов. Он создается классом наблюдателя. Vue определяет 3 наблюдателя.
- наблюдатель рендеринга: шаблон зависит от переменной, которая должна отображаться в представлении, и наблюдатель рендеринга хранится внутри него.
- вычисляемый наблюдатель: вычисляемый наблюдатель хранится внутри вычисляемого свойства.
- пользовательский наблюдатель: пользовательский наблюдатель хранится внутри переменной, наблюдаемой с помощью свойства watch
Очень важно понимать соответствующие роли этих трех наблюдателей, и текст будет посвящен вычисляемому наблюдателю.
Инициализация вычисляемого свойства делится на две части.
- Создание экземпляра вычисляемого Watcher
- Определите функцию получения свойств вычисления
Создание вычисляемого наблюдателя
При инициализации текущего компонента он будет выполнятьсяinitComputed
Метод инициализирует вычисляемое свойство и создает экземпляр вычисляемого наблюдателя для каждого вычисляемого свойства.
Различные экземпляры наблюдателя могут быть сгенерированы путем передачи различных элементов конфигурации при создании экземпляра наблюдателя.{ lazy: true }
, экземпляр наблюдателя — это вычисляемый наблюдатель
Определите функцию получения свойств вычисления
После создания вычисляемого наблюдателя будет определена функция-получатель вычисляемого свойства.Когда мы выполняем функцию вычисляемого свойства, на самом деле мы выполняем следующее:computedGetter
эта функция
computedGetter
Кода очень мало, но это ядро вычисляемого свойства, разберем его шаг за шагом.
грязное имущество
Получите вычисляемый наблюдатель, определенный на первом шаге, с помощью ключа, а затем оцените, является ли свойство dirty вычисляемого наблюдателя истинным.evaluate
метод,evaluate
Функция, которая вычисляет атрибут, будет выполнена внутри, а значение атрибута наблюдателя будет равно результату после выполнения функции, который является окончательным вычисленным значением, о нем мы поговорим позже.
Грязное свойство — это признак того, что текущий вычисляемый Watcher должен выполняться повторно., Это также разница между вычисляемыми свойствами и обычными методами.В сочетании с приведенным выше рисунком можно обнаружить, что если dirty имеет значение false, он не будет выполнен.evaluate
Функция, вычисляющая свойство, выполняться не будет.Вы видите, что watcher.value возвращается непосредственно в конце, указывая на то, что в этот раз вычисление производиться не будет, а будет использовано непосредственно значение предыдущего значения.
при первом срабатыванииcomputedGetter
Когда значение свойства dirty по умолчанию равно true , это происходит потому, что Vue приравнивает свойство dirty к свойству lazy при инициализации вычисляемого наблюдателя, что равно true
Зная, что значение dirty по умолчанию равно true, когда оно ложно? Давайте посмотримevalutate
Бетонная реализация
метод оценки
evaluate
Этот метод уникален для вычисляемого наблюдателя, а код состоит всего из 2 строк.
получить метод
Первая строка выполняетсяget
метод,get
метод — это общий метод, используемый всеми наблюдателями для оценки
get
В основном выполните эти три шага
- Отправка текущего Watcher в виде стека push в стек
- выполнить метод получения
- вытолкнуть этого наблюдателя из стека
Мы знаем, что Vue.js будет поддерживать глобальный стек для хранения наблюдателя.Каждый раз, когда срабатывает геттер внутри реактивной переменной, наблюдатель в верхней части глобального стека (т.е. Dep.target) будет собран, а наблюдатель будет хранится в dep, хранящемся внутри реактивной переменной
первый проходpushTarget
Поместите текущий вычисляемый наблюдатель в глобальный стек, после чего Dep.target указывает на вычисляемый наблюдатель в верхней части стека.
Второй шаг выполняет метод получения для вычисляемого наблюдателя,getter
Метод - это функция, которая рассчитывает свойство, функция выполнения присваивает возвращенное значение в свойство значения, и когда выполняется функция, которая рассчитывает свойство, в том случае, если внутри внутри есть другие отзывчивые переменные, он будет запускать добычу внутри них и Поместите первый шаг в качестве расчетного наблюдателя в верхней части текущего стека, хранятся в объекте DEP внутри реактивной переменной
Обратите внимание на геттеры внутри реактивных переменных и
getter
метод не является функцией
Третий шаг извлекает вычисляемый наблюдатель из глобального стека.
Причина, по которой этот вычисляемый наблюдатель вставляется и выталкивается, заключается в том, чтобы позволить отзывчивым переменным, от которых зависит функция вычисляемого свойства, собирать вычисляемый наблюдатель, когда внутренний геттер выполняется на втором этапе.
Для вычисляемых свойствget
Функция метода заключается в оценке
установить грязное значение false
законченныйget
метод, то естьПосле того, как вычисленное свойство было оценено один раз, для свойства dirty будет установлено значение false, и если геттер этого вычисляемого свойства сработает в следующий раз, фаза оценки будет пропущена напрямую.
Объединить 🌰
В этом примере, поскольку представление должно полагаться на реактивную переменную fullName, оно активирует свой внутренний геттер, а также является вычисляемым свойством, которое будет выполняться.computedGetter
, свойство dirty в настоящее время имеет значение true по умолчанию, выполнитеevaluate
=> get
=> pushTarget
существуетpushTarget
, потому что он выполняется вычисляемым наблюдателемget
метод, поэтому он указывает на этот вычисляемый наблюдатель, помещает его в глобальный стек как Dep.target, а затем выполняет функцию, которая вычисляет свойство
Видно, что функция вычисляемого свойства fullName зависит от двух реагирующих переменных firstName и lastName.Vue внутренне хранит объект dep в форме замыкания.Этот объект dep соберет наблюдателя на вершине текущего стека, то есть собрать полное имя.Вычисляемый наблюдатель вычисляемого свойства, поэтому, когда функция вычисляемого свойства выполняется, вычисляемый наблюдатель будет сохранен в объекте dep внутри firstName и lastName
После завершения сбора извлеките вычисляемый наблюдатель, чтобы восстановить стек в его предыдущее состояние.
метод зависимости
Второй характеристикой вычисляемого свойства является егоdepend
метод, уникальный для вычисляемого наблюдателя
Когда Dep.target существует, это означает, что в глобальном стеке есть еще другие наблюдатели после того, как вычисленный наблюдатель был извлечен на предыдущем шаге. Например, когда представление зависит от текущего вычисляемого свойства, наблюдатель наверху стека является наблюдателем рендеринга, или если другое вычисляемое свойство внутренне зависит от текущего вычисляемого свойства, наблюдателем наверху стека может быть другой наблюдатель. вычисляемый наблюдатель, в любом случае Всякий раз, когда это вычисляемое свойство используется где-либо, оноdepend
метод
наблюдательdepend
метод:
depend
Этот метод также очень короткий, он будет проходить через свойство deps текущего вычисляемого наблюдателя и, в свою очередь, выполнять метод зависимости dep.
Что такое deps? Как упоминалось ранее, dep — это объект, хранящийся внутри каждой переменной, отвечающей за отклик. Deps можно представить как набор отложений во всех переменных, отвечающих за отклик. Что такое конкретные переменные? На самом деле друзья, понявшие принцип отзывчивости, должны знать, что этот депс на самом деле спасаетВсе объекты DEP внутри реактивных переменных, которые собирают текущий наблюдатель
Это взаимозависимая связь, dep внутри каждой реактивной переменной сохранит все наблюдатели, а свойство deps каждого наблюдателя сохранит все объекты dep, собранные в реактивной переменной этого наблюдателя.
(Причина, по которой Vue сохраняет зависимости в наблюдателе, заключается в том, что, с одной стороны, ему нужно разрешить вычисляемым свойствам собирать зависимости, а с другой стороны, он также может знать, какие зависимости зависят от наблюдателя при выходе из наблюдателя. просто вызовите соответствующий метод выхода из системы в отл. )
Затем он переходит каждый DEP и выполняет метод dep.depend:
Роль этого метода состоит в том, чтобы собирать текущие внутренние переменные отклика dep в текущей вершине стека наблюдателя формулы, например, потому что представление fullName зависит, поэтому, когдаget
После завершения выполнения метода всплывает вычисляемый наблюдатель, а наблюдатель наверху стека становится исходным наблюдателем рендеринга.
DEPS Properties Computed Watcher в хранимых двух DEP, одно из FirstName DEP, другой - это фамилию DEP, как в реализации этих двух переменныхget
Компьютерный наблюдатель собирается на втором этапе метода
В это время, когда выполняется dep.depend, наблюдатель на вершине стека, то есть наблюдатель рендеринга, будет снова собран для этих двух отзывчивых переменных Наконец, dep внутри этих двух переменных сохраняет две переменные, a вычисляемый наблюдатель и наблюдатель рендеринга.
В конце концов верните watcher.value как значение, отображаемое в представлении.
Измените переменную, от которой зависит вычисляемое свойство.
Как упоминалось ранее, вычисляемое свойство будет пересчитываться для создания нового значения только при изменении зависимостей вычисляемого свойства.При изменении других переменных в представлении и обновлении представления вычисляемое свойство не будет пересчитываться. Как это сделать?
При расчете зависимости свойства, когда Имя и фамилия изменяются, внутренний сеттер, Vue будет проходить наблюдателя, который будет сохранен в ответ на DEP Safed, в конечном итоге выполнит каждого наблюдателя.update
метод
можно увидетьupdate
Существует 3 случая метода:
- ленивый: существует только в вычисленном наблюдателе
- синхронизация: существует только в наблюдателе пользователя. Когда наблюдатель пользователя устанавливает синхронизацию, наблюдатель будет вызываться синхронно и не будет отложен до следующего тика, поэтому он не будет использоваться.
- По умолчанию: будет выполняться обычный пользовательский наблюдатель и наблюдатель за рендерингом.
queueWatcher
, поместите этих наблюдателей в nextTick и выполните
через предыдущийevaluate
а такжеdepend
Метод, dep внутри firstName и lastName сохранит 2 наблюдателя, вычисляемый наблюдатель и наблюдатель рендеринга. Когда lastName изменяется, внутренний сеттер будет запущен для обхода всех наблюдателей, сохраненных dep. Здесь вычисленный наблюдатель будет выполнен первый.update
метод
В то же время ранее упоминалось, что после завершения оценки вычисляемого наблюдателя, dirty будет установлено в false, а затем значение вычисляемого свойства будет пропущено.evaluate
Метод возвращает предыдущее значение напрямую и выполняет вычисленный наблюдатель.update
Метод снова превратится в грязный в истину, и весь вычисляемый наблюдатель будет делать только это, то есть отменяет использование вычисляемым наблюдателем предыдущего флага кеша.
Эта операция выполняется синхронно, то есть, даже если наблюдатель рендеринга или наблюдатель пользователя опережает вычисляемый наблюдатель в массиве наблюдателей, поскольку первые два наблюдателя обычно выполняются асинхронно, вычисляемый наблюдатель будет выполняться первым в конечном исполнение.
а такжеНастоящая оценка выполняется в наблюдателе рендеринга., при переходе ко второму наблюдателю рендеринга, поскольку представление зависит от fullName, сработает геттер вычисляемого свойства, и снова будет выполнен предыдущий.computedGetter
, в это время, поскольку предыдущий шаг превратился в грязный в истинный, он войдетevalutate
Пересчитаем, в это время fullName получает последнее значение "You Da"
Не изменять свойства, зависящие от расчета переменных
Вернемся к началу метода расчета и примерам различий атрибутов, поскольку представление зависит от otherProp, поэтому при изменении переменной оно вызывает сохранение внутреннего наблюдателя рендеринга dep.update
метод, который запоминает зависимости для обновления представления
Когда метод methodFullName собирается, поскольку это общий метод, Vue будет выполнять соответствующий метод каждый раз, когда обновляется представление, поэтому «метод» будет печататься каждый раз, а когда будет собран CompededFullName, он будет выполнен.computedGetter
, но поскольку otherPorp не является переменной, от которой зависит это вычисляемое свойство, вычисляемый наблюдатель не был запущенupdate
, поэтому свойство dirty равно false, оно будет пропущено.evaluate
Метод возвращает кэшированный результат напрямую, поэтому он не печатает «вычислено» каждый раз.
Суммировать
Только при изменении переменной ответа, зависящей от свойства, атрибут вычисления будет пересчитан, в противном случае будет использоваться первое значение кэша, поскольку атрибут dirty внутри свойства вычисляется, если свойство dirty имеет значение false Всегда будет использовать предыдущее значение кэша
Dep внутри реактивной переменной, от которой зависит вычислительный атрибут, сохранит вычисляемый наблюдатель, и когда они будут изменены, вычисленный наблюдатель будет запущен.update
метод, установите для грязного флага значение true, чтобыв следующий разПересчет запускается, когда другие наблюдатели полагаются на это вычисляемое свойство.