Предварительный
Я некоторое время читал исходный код Vue, начиная с его основного принципа, и начал изучать исходный код, и его основным принципом является отзывчивость его данных.Когда дело доходит до принципа отзывчивости Vue, мы можем начать из-за его совместимости.Vue не поддерживает браузеры ниже IE8, потому что Vue основан наObject.definePropertyдля получения ответа на данные и Object.defineProperty — это функция, которую нельзя скрыть в ES5, поэтому Vue не поддерживает браузеры IE8 и более ранних версий; Vue пропускает Object.defineProperty.getter/setterОтслеживайте собранные зависимости, уведомляйте об изменениях при доступе к свойствам и их изменении, а затем обновляйте данные представления;
Ограничено современным JavaScript (и устарело)Object.observe), Vue не может обнаружить добавление или удаление свойств объекта. Поскольку Vue будет выполнять свойства свойств при инициализации экземпляраgetter/setterпроцесс преобразования, поэтому атрибут должен быть вdataОбъект существует для того, чтобы Vue преобразовывал его так, чтобы он реагировал.
Мы здесь, чтобы проанализировать исходный код Vue 2.3, Реагирующие изменения данных Vue в основном включаютObserver, Watcher , DepЭто три основных класса, поэтому, чтобы понять реактивные изменения Vue, вам нужно понять, как эти три класса работают и соединяются, а также их принципы и ответственные логические операции. Затем мы анализируем принцип отзывчивости Vue из кода простого экземпляра Vue.
var vue = new Vue({
el: "#app",
data: {
name: 'Junga'
},
created () {
this.helloWorld()
},
methods: {
helloWorld: function() {
console.log('my name is' + this.name)
}
}
...
})
Экземпляр инициализации Vue
Согласно ВьюЖизненный циклМы знаем, что Vue сначала выполнит операцию инициализации init; исходный код находится вsrc/core/instance/init.jsсередина
/*初始化生命周期*/
initLifecycle(vm)
/*初始化事件*/
initEvents(vm)Object.defineProperty
/*初始化render*/
initRender(vm)
/*调用beforeCreate钩子函数并且触发beforeCreate钩子事件*/
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
/*初始化props、methods、data、computed与watch*/
initState(vm)
initProvide(vm) // resolve provide after data/props
/*调用created钩子函数并且触发created钩子事件*/
callHook(vm, 'created')
Вы можете увидеть приведенный выше кодinitState(vm)используется для инициализации свойств, методов, данных, вычислений и просмотра;
/*初始化props、methods、data、computed与watch*/
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
/*初始化props*/
if (opts.props) initProps(vm, opts.props)
/*初始化方法*/
if (opts.methods) initMethods(vm, opts.methods)
/*初始化data*/
if (opts.data) {
initData(vm)
} else {
/*该组件没有data的时候绑定一个空对象*/
observe(vm._data = {}, true /* asRootData */)
}
/*初始化computed*/
if (opts.computed) initComputed(vm, opts.computed)
/*初始化watchers*/
if (opts.watch) initWatch(vm, opts.watch)
}
...
/*初始化data*/
function initData (vm: Component) {
/*得到data数据*/
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}defi
...
//遍历data中的数据
while (i--) {
/*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
/*判断是否是保留字段*/
/*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
proxy(vm, `_data`, keys[i])
}
}
// observe data
/*这里通过observe实例化Observe对象,开始对数据进行绑定,asRootData用来根数据,用来计算实例化根数据的个数,下面会进行递归observe进行对深层对象的绑定。则asRootData为非true*/
observe(data, true /* asRootData */)
}
1. данные инициализации
Теперь сосредоточимся на анализеinitData, здесь в основном две вещи: одна — проксировать данные выше _data на виртуальную машину, а другая — выполнять наблюдение (данные, истина / asRootData /) Сделать все данные наблюдаемыми, то есть выполнять операции получения/установки для каждого свойства, определенного данными, что является основой для Vue для реализации отзывчивости;observeРеализацияsrc/core/observer/index.js
/*尝试创建一个Observer实例(__ob__),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value)) {
return
}
let ob: Observer | void
/*这里用__ob__这个属性来判断是否已经有Observer实例,如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,如果已有Observer实例则直接返回该Observer实例,这里可以看Observer实例化的代码def(value, '__ob__', this)*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。而且该对象在shouldConvert的时候才会进行Observer。这是一个标识位,避免重复对value进行Observer
*/
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
/*如果是根数据则计数,后面Observer中的observe的asRootData非true*/
ob.vmCount++
}
return ob
}
здесьnew Observer(value)Это один из основных методов реализации отзывчивости, с помощью которого данные могут быть преобразованы в наблюдаемые, и вот что мы сказали в начале, используяObject.definePropertyреализует данныеgetter/setterоперация, черезWatcherНаблюдать за изменениями в данных, а затем обновлять представление.
2. Наблюдатель
Класс Observer преобразует значение ключа каждого целевого объекта (т. е. данных) в форму получения/установки для сбора зависимостей и планирования обновлений.
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
/* 将Observer实例绑定到data的__ob__属性上面去,之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,def方法定义可以参考/src/core/util/lang.js*/
def(value, '__ob__', this)
if (Array.isArray(value)) {
/*如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。*/
const augment = hasProto
? protoAugment /*直接覆盖原型的方法来修改目标对象*/
: copyAugment /*定义(覆盖)目标对象或数组的某一个方法*/
augment(value, arrayMethods, arrayKeys)
/*如果是数组则需要遍历数组的每一个成员进行observe*/
this.observeArray(value)
} else {
/*如果是对象则直接walk进行绑定*/
this.walk(value)
},
walk (obj: Object) {
const keys = Object.keys(obj)
/*walk方法会遍历对象的每一个属性进行defineReactive绑定*/
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
- Сначала привяжите экземпляр Observer к даннымobПерейти к свойству, чтобы предотвратить повторную привязку;
- Если данные представляют собой массив, сначала реализуйте соответствующийМетод мутации(Метод мутации здесь означает, что Vue переписывает 7 нативных методов массива, которые не будут здесь описываться и будут описаны позже), а затем наблюдает за каждым членом массива, чтобы сделать его чувствительными к данным;
- В противном случае выполните метод walk (), просмотрите все данные в данных и выполните привязку геттера / сеттера.Основной метод здесьdefineReative(obj, keys[i], obj[keys[i]])
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
/*在闭包中定义一个dep对象*/
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/*如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。*/
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/*对象的子对象递归进行observe并返回子节点的Observer对象*/
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/*如果原本对象拥有getter方法则执行*/
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/*进行依赖收集*/
dep.depend()
if (childOb) {
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
childOb.dep.depend()
}
if (Array.isArray(value)) {
/*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
dependArray(value)
}
}
return value
},
set: function reactiveSetter (newVal) {
/*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
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()
}
if (setter) {
/*如果原本对象拥有setter方法则执行setter*/
setter.call(obj, newVal)
} else {
val = newVal
}
/*新的值需要重新进行observe,保证数据响应式*/
childOb = observe(newVal)
/*dep对象通知所有的观察者*/
dep.notify()
}
})
}
Где метод получения:
- Сначала объявите по одному для каждых данныхDepОбъект-экземпляр, который используется геттером для выполнения dep.depend() для сбора связанных зависимостей;
- По Dep.target определить, следует ли собирать зависимости или общие значения. Что такое Dep.target и как его собирать, будет объяснено позже, сначала вкратце разберитесь с его функцией,
Итак, вопрос в том, зачем нам собирать связанные зависимости?
new Vue({
template:
`<div>
<span>text1:</span> {{text1}}
<span>text2:</span> {{text2}}
<div>`,
data: {
text1: 'text1',
text2: 'text2',
text3: 'text3'
}
});
Из приведенного выше кода видно, что text3 в данных на самом деле не используется шаблоном. Чтобы повысить эффективность выполнения кода, нам не нужно выполнять для него адаптивную обработку. Поэтому простое понимание сбора зависимостей таково: коллекция используется только на реальных страницах.Полученные данные данных, а затем помечены, здесь помечены как Dep.target.
В методе установки:
- Получите новые значения и наблюдайте за тем, чтобы обеспечить отзывчивость данных;
- Объект dep используется для уведомления всех наблюдателей о необходимости обновления данных для достижения эффекта реагирования.
В классе Observer мы видим, что при использовании геттера dep будет собирать соответствующие зависимости, то есть наблюдатель, который собирает зависимости, а затем уведомлять наблюдателя через dep во время операции установки. будет выполнять изменения. Мы опишем это с помощью рисунка. Отношения между тремя:
Из рисунка мы можем просто понять: Dep можно рассматривать как книжный магазин, Watcher — подписчик книжного магазина, а Observer — книга книжного магазина, подписчики могут добавлять информацию о подписчике, когда они подписываются на книги в книжном магазине, и когда новая книга доступна , он будет подписан через книжный магазин, чтобы отправить сообщение.
3. Наблюдатель
Watcher — это объект-наблюдатель. После того, как зависимость будет собрана, объект Watcher будет сохранен в подпрограммах Dep. Когда данные изменятся, Dep уведомит экземпляр Watcher, а затем экземпляр Watcher вызовет cb для обновления представления.
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
/*_watchers存放订阅者实例*/
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
/*把表达式expOrFn解析成getter*/
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
/*获得getter的值并且重新进行依赖收集*/
get () {
/*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
pushTarget(this)
let value
const vm = this.vm
/*执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
在将Dep.target设置为自生观察者实例以后,执行getter操作。
譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
那么在执行getter的时候就会触发a跟c两个数据的getter函数,
在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
将该观察者对象放入闭包中的Dep的subs中去。*/
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
/*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
if (this.deep) {
/*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
traverse(value)
}
/*将观察者实例从target栈中取出并设置给Dep.target*/
popTarget()
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
/*添加一个依赖关系到Deps集合中*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
/*清理依赖收集*/
cleanupDeps () {
/*移除所有观察者对象*/
...
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
/*
调度者接口,当依赖发生改变的时候进行回调。
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/*同步则执行run直接渲染视图*/
this.run()
} else {
/*异步推送到观察者队列中,下一个tick时调用。*/
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
/*
调度者工作接口,将被调度者回调。
*/
run () {
if (this.active) {
/* get操作在获取value本身也会执行getter从而调用update更新视图 */
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/*
即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
*/
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/*设置新的值*/
this.value = value
/*触发回调*/
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
/*获取观察者的值*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
/*收集该watcher的所有deps依赖*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
/*将自身从所有依赖收集订阅列表删除*/
teardown () {
...
}
}
4. Деп
Активировано данными наблюдателяgetterчас,Depбудет собирать зависимостиWatcher,фактическиDepКак я уже сказал, это книжный магазин, который может принимать подписки от нескольких подписчиков.Когда появится новая книга, то есть когда изменятся данные, она пройдетDepДатьWatcherУведомлять об обновлениях.
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
/*添加一个观察者对象*/
addSub (sub: Watcher) {
this.subs.push(sub)
}
/*移除一个观察者对象*/
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/*依赖收集,当存在Dep.target的时候添加观察者对象*/
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/*通知所有订阅者*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Суммировать
по фактуVueКогда рендеринг инициализируется в представлении, данные, привязанные к представлению, будут создавать экземплярWatcher, сбор зависимостей осуществляется через атрибутыgetterФункция завершена, как было сказано в начале статьи.Observer,Watcher,DepОба связаны с коллекцией зависимостей. вObserverа такжеDepэто отношение один к одному,Depа такжеWatcherявляется отношением многие ко многим.DepявляетсяObserverа такжеWatcherсвязь между. После завершения сбора зависимостей при изменении атрибута он будет выполнен.Observerобъектdep.notify()метод, этот метод будет проходить по списку подписчиков (Watcher) для отправки ему сообщений,Watcherбудет выполнятьrunЧтобы обновить вид, давайте посмотрим на картинку, чтобы подвести итог:
- существуетVueДиректива или привязка данных во время компиляции шаблона создаст экземплярWatcherэкземпляр, который будет запущен в процессе создания экземпляраget()указать на себяDep.target;
- данные вObserverвыполнить, когдаgetterвызоветdep.depend()Выполнить сбор зависимостей, результат сбора зависимостей: 1. Данные находятся вObserverКогда добавляются подпрограммы экземпляра dep замыкания для наблюдения за егоWatcherпример; 2.WatcherДобавьте объект наблюдения в depsObserverзакрытие от когда
- когда данныеObserverПосле изменения значения объекта инициируйте выполнение наблюдателя в подпрограммах, чтобы наблюдать за ним.update()метод и, наконец, вызвать функцию обратного вызова наблюдателя cb, а затем обновить представление.