предисловие
computed
существуетVue
Это очень часто используемая конфигурация свойств, которая может меняться при изменении свойств зависимостей, что очень удобно. Тогда эта статья даст вам полное представлениеcomputed
внутренние принципы и рабочий процесс.
Перед этим, я надеюсь, вы сможете получить некоторое представление о реактивных принципах, потому чтоcomputed
Работа основана на принципе отзывчивым. Если вы не очень понимаете принципы реагирования, вы можете прочитать мою предыдущую статью:Рука об руку, чтобы помочь вам понять принцип отзывчивости Vue
расчетное использование
Если вы хотите понять принцип, самое главное — знать, как его использовать, что поможет вам понять это позже.
Первое, объявление функции:
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
Во-вторых, объявление объекта:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Напоминание: Атрибуты данных, используемые в вычислении, в дальнейшем вместе именуются «атрибутами зависимостей».
процесс работы
Сначала понятьcomputed
Примерный процесс, чтобы увидеть, что является основной точкой вычислительных свойств.
Входной файл:
// 源码位置:/src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
_init
:
// 源码位置:/src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
// mergeOptions 对 mixin 选项和 new Vue 传入的 options 选项进行合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
// 初始化数据
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initState
:
// 源码位置:/src/core/instance/state.js
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
:
// 源码位置:/src/core/instance/state.js
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
// 1
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]
// 2
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
// create internal watcher for the computed property.
// 3
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{ lazy: true }
)
}
// 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)) {
// 4
defineComputed(vm, key, userDef)
}
}
}
- Определяется в экземпляре
_computedWatchers
Объект, используемый для хранения «вычисленных свойств»Watcher
" - получить вычисляемое свойство
getter
, вам нужно определить, является ли это объявлением функции или объявлением объекта - Создайте «вычисляемое свойство»
Watcher
",getter
Переданный в качестве параметра, он будет вызываться при обновлении свойства зависимости, и вычисляемое свойство будет переоценено. требует вниманияWatcher
изlazy
Конфигурация, которая является идентификатором реализации кеша -
defineComputed
Перехват данных вычисляемых свойств
defineComputed
:
// 源码位置:/src/core/instance/state.js
const noop = function() {}
// 1
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 判断是否为服务端渲染
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
// 2
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
// 3
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
// 4
Object.defineProperty(target, key, sharedPropertyDefinition)
}
-
sharedPropertyDefinition
является начальным объектом описания свойства вычисляемого свойства - Когда вычисляемое свойство объявляется с помощью функции, установите объект описания свойства.
get
а такжеset
- Когда вычисляемое свойство объявляется с использованием объекта, установите объект описания свойства.
get
а такжеset
- Перехват данных вычисляемых свойств,
sharedPropertyDefinition
Передать в качестве третьего параметра
Рендеринг на стороне клиента используетcreateComputedGetter
Создайтеget
, рендеринг на стороне сервера используетcreateGetterInvoker
Создайтеget
. Между ними есть большая разница: рендеринг на стороне сервера не кэширует вычисляемые свойства, а оценивает их напрямую:
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
Но обычно мы больше говорим о рендеринге на стороне клиента, давайте посмотримcreateComputedGetter
реализация.
createComputedGetter
:
// 源码位置:/src/core/instance/state.js
function createComputedGetter (key) {
return function computedGetter () {
// 1
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 2
if (watcher.dirty) {
watcher.evaluate()
}
// 3
if (Dep.target) {
watcher.depend()
}
// 4
return watcher.value
}
}
}
Это ядро реализации вычисляемого свойства,computedGetter
То есть он срабатывает, когда вычисляемое свойство выполняет перехват данных.get
.
- над
initComputed
функция, "вычисляемое свойствоWatcher
" хранится в экземпляре_computedWatchers
, здесь вынесите соответствующее "вычисляемое свойство"Watcher
" -
watcher.dirty
является триггерной точкой для реализации кэширования вычисляемых свойств,watcher.evaluate
Повторная оценка вычисляемых свойств - Коллекция свойств зависимостей "рендеринг"
Watcher
" - Вычисляемое свойство оценивает и сохраняет значение в
value
середина,get
Возвращает значение вычисляемого свойства
Кэширование и обновление вычисляемого свойства
тайник
Далее мы будемcreateComputedGetter
Разделите, проанализируйте их отдельные рабочие процессы. Это триггер для кэширования:
if (watcher.dirty) {
watcher.evaluate()
}
см. далееWatcher
Соответствующая реализация:
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
// dirty 初始值等同于 lazy
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.value = this.lazy
? undefined
: this.get()
}
}
Также не забудьте создать "вычисляемое свойство"Watcher
", настроеноlazy
правда.dirty
Начальное значение эквивалентноlazy
. Поэтому, когда рендеринг страницы инициализируется и значение вычисляемого свойства извлекается, оно будет выполнено один раз.watcher.evaluate
.
evaluate() {
this.value = this.get()
this.dirty = false
}
После оценки присвоить значениеthis.value
,надcreateComputedGetter
внутриwatcher.value
Просто обновите здесь. тогдаdirty
Установите значение false, если свойство зависимости не изменится, при следующем извлечении значения оно не будет выполнено.watcher.evaluate
, а напрямую вернутьсяwatcher.value
, который реализует механизм кэширования.
возобновить
Когда свойство зависимости будет обновлено, оно будет вызыватьсяdep.notify
:
notify() {
this.subs.forEach(watcher => watcher.update())
}
затем выполнитьwatcher.update
:
update() {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
Как «рассчитать недвижимостьWatcher
"изlazy
правда, вотdirty
будет установлено значение true. Дождитесь, пока рендеринг страницы примет значение вычисляемого свойства и будет выполнено условие точки срабатывания, выполнитеwatcher.evaluate
Выполните повторную оценку, и вычисленное свойство обновится соответствующим образом.
Свойства зависимостей собирают зависимости
Соберите средство наблюдения за вычисляемыми свойствами
При инициализации рендеринг страницы будет «рендеритьWatcher
"Вставить в стек и установить наDep.target
Вычисляемое свойство встречается во время рендеринга страницы, и его значение получено, поэтому выполнитеwatcher.evaluate
логика, затем позвонитеthis.get
:
get () {
// 1
pushTarget(this)
let value
const vm = this.vm
try {
// 2
value = this.getter.call(vm, vm) // 计算属性求值
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
popTarget()
this.cleanupDeps()
}
return value
}
Dep.target = null
let stack = [] // 存储 watcher 的栈
export function pushTarget(watcher) {
stack.push(watcher)
Dep.target = watcher
}
export function popTarget(){
stack.pop()
Dep.target = stack[stack.length - 1]
}
pushTarget
Это "вычисляемое свойство"Watcher
"Вставить в стек и установить наDep.target
, в это время стек [рендеринг Watcher, вычисление свойства Watcher]
this.getter
Оценивать вычисляемые свойства, запускать перехват данных свойств зависимостей при их полученииget
,воплощать в жизньdep.depend
собирать зависимости ("вычисленное свойствоWatcher
»)
Сбор рендеринга Watcher
this.getter
После завершения оценкиpopTragte
, "вычисленные свойстваWatcher
«Из стека,Dep.target
установить на "рендеринг"Watcher
", В настоящее времяDep.target
это "рендерингWatcher
"
if (Dep.target) {
watcher.depend()
}
watcher.depend
Соберите зависимости:
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
deps
Внутреннее хранилище является свойством зависимостиdep
, этот шаг представляет собой зависимость коллекции свойств зависимостей ("рендерингWatcher
»)
После того, как две вышеуказанные зависимости собраны, свойство зависимостиsubs
хранить дваWatcher
, [наблюдатель вычисляемых свойств, наблюдатель отрисовки]
Почему свойства зависимостей собирают наблюдатель рендеринга
Когда я впервые прочитал исходный код, было странно, что свойства зависимостей собирают «вычисляемые свойства».Watcher
«Разве это не хорошо? Почему свойства зависимостей все еще собираются» рендерингWatcher
"?
Сценарий 1. В шаблоне используются как свойства зависимостей, так и вычисляемые свойства.
<template>
<div>{{msg}} {{msg1}}</div>
</template>
export default {
data(){
return {
msg: 'hello'
}
},
computed:{
msg1(){
return this.msg + ' world'
}
}
}
Шаблоны используют свойства зависимости. Когда страница отображает значение свойства зависимости, свойство зависимости сохраняет значение «рендеринг».Watcher
",такwatcher.depend
Этот шаг представляет собой повторяющийся сбор, ноwatcher
Внутреннее будет разгружено.
Вот почему я сомневаюсь,Vue
Для хорошей структуры должна быть причина. Поэтому я подумал о другом сценарии, который можно разумно объяснить.watcher.depend
эффект.
Второй сценарий: в шаблоне используются только вычисляемые свойства
<template>
<div>{{msg1}}</div>
</template>
export default {
data(){
return {
msg: 'hello'
}
},
computed:{
msg1(){
return this.msg + ' world'
}
}
}
Свойства зависимостей не используются в шаблоне. При отображении страницы свойства зависимостей не будут собирать «рендеринг».Watcher
". В настоящее время в свойствах зависимостей будут только "вычисляемые свойства"Watcher
", когда свойство зависимости изменяется, активируется только "вычисляемое свойство"Watcher
"изupdate
. Расчет собственностиupdate
будет толькоdirty
Установите значение true, и вычисляемое свойство не будет обновляться, если оно не оценивается немедленно.
Итак, вам нужно собрать «рендерингWatcher
", после выполнения "вычисленного свойства"Watcher
", а затем выполните "RenderWatcher
". Рендеринг страницы принимает значение вычисляемого свойства и выполняетwatcher.evaluate
Оценка будет пересчитана, а вычисляемое свойство страницы будет обновлено.
Суммировать
Принцип вычисляемых свойств и принцип реагирования аналогичны. То же самое касается использования перехвата данных и сбора зависимостей. Разница в том, что вычисляемые свойства кэшируются и оптимизируются и будут переоцениваться только при изменении свойств зависимостей. в других случаях возвращайте непосредственно значение кэша. Сервер не кэширует вычисляемые свойства.
Предпосылка обновления вычислительных свойств требует «рендерингаWatcher
'', поэтому свойство зависимостиsubs
будет хранить не менее двухWatcher
.