Статья впервые опубликована вgithub Blog.
Эта статья основана наИсходный код Vuev2.x для анализа. Здесь отсортированы только самые важные части исходного кода, а некоторые неосновные части пропущены. Реактивные обновления в основном включаютWatcher
,Dep
,Observer
эти основные категории.
В этой статье в основном разъясняются следующие вопросы, которые легко спутать:
-
Watcher
,Dep
,Observer
Отношения между этими классами? -
Dep
серединаsubs
Что хранится? -
Watcher
серединаdeps
Что хранится? -
Dep.target
Что это такое и где присваивается значение?
Эта статья начинается непосредственно с нового экземпляра Vue и шаг за шагом раскрывает принцип адаптивности Vue, предполагая следующий простой код Vue:
var vue = new Vue({
el: "#app",
data: {
counter: 1
},
watch: {
counter: function(val, oldVal) {
console.log('counter changed...')
}
}
})
1. Инициализация экземпляра Vue
из ВьюЖизненный циклИзвестно, что первыйinit
Операция инициализации, эта часть кода находится вinstance/init.js
середина.
src/core/instance/init.js
initLifecycle(vm) // vm生命周期相关变量初始化操作
initEvents(vm) // vm事件相关初始化
initRender(vm) // 模板解析相关初始化
callHook(vm, 'beforeCreate') // 调用beforeCreate钩子函数
initInjections(vm) // resolve injections before data/props
initState(vm) // vm状态初始化(重点在这里)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 调用created钩子函数
Вышеупомянутый источникinitState(vm)
Это фокус исследования, который реализуетсяprops
,methods
,data
,computed
,watch
операция инициализации. Здесь, основываясь на приведенном выше примере, сосредоточьтесь наdata
а такжеwatch
, Местоположение источникаinstance/state.js
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) // 对vm的data进行初始化,主要是通过Observer设置对应getter/setter方法
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
// 对添加的watch进行初始化
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
2. initData
Экземпляр Vue реализует для каждого из своих данныхgetter/setter
метод, который является основой для реализации отзывчивости. оgetter/setter
видимыйMDN web docs. Проще говоря, он принимает значениеthis.counter
При можно настроить некоторые операции, а затем вернуть значение счетчика; при изменении значенияthis.counter = 10
При установке значения вы также можете настроить некоторые операции при установке значения.initData(vm)
Реализация находится в исходном кодеinstance/state.js
.
src/core/instance/state.js
while (i--) {
...
// 这里将data,props,methods上的数据全部代理到vue实例上
// 使得vm.counter可以直接访问
}
// 这里略过上面的代码,直接看最核心的observe方法
// observe data
observe(data, true /* asRootData */)
здесьobserve()
Метод превращает данные в наблюдаемые, почему они наблюдаемые? в основном реализованоgetter/setter
метод, пустьWatcher
Можно наблюдать изменения этих данных. Посмотрите нижеobserve
реализация.
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value) // 重点在这里,响应式的核心所在
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
Просто сосредоточься здесьnew Observer(value)
, который является ядром метода, путемObserver
класс будет вьюdata
стать отзывчивым. Согласно нашему примеру, в это время параметрvalue
Значение{ counter: 1 }
. Рассмотрим подробно нижеObserver
Добрый.
3. Observer
Сначала посмотрите на конструктор этого класса,new Observer(value)
Конструктор выполняется первым. В примечании автора говорится, что класс Observer преобразует значение ключа каждого целевого объекта (то есть данные в данных) вgetter/setter
форма для сбора и обновления зависимостей через уведомление о зависимостях.
/**
* Observer class that are attached to each observed
* object. Once attached, the observer converts target
* object's property keys into getter/setters that
* collect dependencies and dispatches updates.
*/
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
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value) // 遍历data对象中{counter : 1, ..} 中的每个键值(如counter),设置其setter/getter方法。
}
}
...
}
Самое главное здесьthis.walk(value)
метод,this.observeArray(value)
Это обработка данных массива для достижения соответствующегоМетод мутации, здесь не рассматривается.
Продолжай читатьwalk()
метод, описанный в комментарияхwalk()
Что он делает, так это проходит данные каждой настройки в объекте данных и преобразует их вsetter/getter
.
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
Затем соответствующие данные окончательно преобразуются вgetter/setter
путьdefineReactive()
метод. Также из названия метода легко понять, что метод определен как отзывчивый.В сочетании с первоначальным примером вызов здесьdefineReactive(...)
как показано на рисунке:
Исходный код выглядит следующим образом:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// dep 为当前数据的依赖实例
// dep 维护着一个subs列表,保存依赖与当前数据(此时是当前数据是counter)的观察者(或者叫订阅者)。观察者即是Watcher实例。
const dep = new Dep() ---------------(1)
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
// 定义getter与setter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 这里在获取值之前先进行依赖收集,如果Dep.target有值的话。
if (Dep.target) { -----------------(2)
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 依赖收集完后返回值
return value
},
...
}
Первый взглядgetter
метод, есть два наиболее важных аспекта этого метода.
- объявить по одному для каждых данных
dep
объект экземпляра, затемdep
На замыкание ссылаются соответствующие данные. Например, каждый разcounter
Когда значение извлекается или изменяется, к его экземпляру dep можно получить доступ, и он не исчезнет. - согласно с
Dep.target
Чтобы определить, следует ли собирать зависимости или общие значения. здесьDep.target
Задание последует позже, здесь мы впервые узнаем, что есть такое.
тогда посмотри еще разsetter
метод, исходный код выглядит следующим образом:
set: function reactiveSetter (newVal) {
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.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 最重要的是这一步,即通过dep实例通知观察者我的数据更新了
dep.notify()
}
На этом инициализация данных экземпляра Vue в основном завершена.Давайте рассмотрим следующий рисунок.initData
процесс:
Далее следуетwatch
инициализация:
export function initState (vm: Component) {
...
if (opts.data) {
initData(vm) // 对vm的data进行初始化,主要是通过Observer设置对应getter/setter方法
}
// initData(vm) 完成后进行 initWatch(..)
...
// 对添加的watch进行初始化
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
4. initWatch
здесьinitWatch(vm, opts.watch)
Это соответствует нашему примеру следующим образом:
initWatch
Исходный код выглядит следующим образом:
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
// handler 是观察对象的回调函数
// 如例子中counter的回调函数
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
createWatcher(vm, key, handler)
основывается на входных параметрахWatcher
Пример информации, исходный код выглядит следующим образом:
function createWatcher (
vm: Component,
keyOrFn: string | Function,
handler: any,
options?: Object
) {
// 判断是否是对象,是的话提取对象里面的handler方法
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 判断handler是否是字符串,是的话说明是vm实例上的一个方法
// 通过vm[handler]获取该方法
// 如 handler='sayHello', 那么handler = vm.sayHello
if (typeof handler === 'string') {
handler = vm[handler]
}
// 最后调用vm原型链上的$watch(...)方法创建Watcher实例
return vm.$watch(keyOrFn, handler, options)
}
$watch
Это метод, определенный в цепочке прототипов Vue.Исходный код выглядит следующим образом:
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
// 创建Watcher实例对象
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
// 该方法返回一个函数的引用,直接调用该函数就会调用watcher对象的teardown()方法,从它注册的列表中(subs)删除自己。
return function unwatchFn () {
watcher.teardown()
}
}
После серии инкапсуляций мы, наконец, видим создание экземпляра объекта Watcher. Это будет подробно объяснено нижеWatcher
Добрый.
5. Watcher
Согласно нашему примеру,new Watcher(...)
Как показано ниже:
сначала выполнитьWatcher
Структура класса, исходный код выглядит следующим образом, а какой-то код опущен:
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
...
this.cb = cb // 保存传入的回调函数
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = [] // 保存观察数据当前的dep实例对象
this.newDeps = [] // 保存观察数据最新的dep实例对象
this.depIds = new Set()
this.newDepIds = new Set()
// parse expression for getter
// 获取观察对象的get方法
// 对于计算属性, expOrFn为函数
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 通过parsePath方法获取观察对象expOrFn的get方法
this.getter = parsePath(expOrFn)
...
}
// 最后通过调用watcher实例的get()方法,
// 该方法是watcher实例关联观察对象的关键之处
this.value = this.lazy
? undefined
: this.get()
}
parsePath(expOrFn)
Конкретный метод реализации заключается в следующем:
/**
* Parse simple path.
*/
const bailRE = /[^\w.$]/ // 匹配不符合包含下划线的任意单词数字组合的字符串
export function parsePath (path: string): any {
// 非法字符串直接返回
if (bailRE.test(path)) {
return
}
// 举例子如 'counter'.split('.') --> ['counter']
const segments = path.split('.')
// 这里返回一个函数给this.getter
// 那么this.getter.call(vm, vm),这里vm就是返回函数的入参obj
// 实际上就是调用vm实例的数据,如 vm.counter,这样就触发了counter的getter方法。
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
Это ловко возвращает метод вthis.getter
, который:
this.getter = function(obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
this.getter
будетthis.get()
Вызывается в методе для получения значения наблюдаемого объекта и запуска его коллекции зависимостей, вот получениеcounter
ценность .
Последний шаг конструктора Watcher, вызовthis.get()
метод, методисходный кодследующим образом:
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 该方法实际上是设置Dep.target = this
// 把Dep.target设置为该Watcher实例
// Dep.target是个全局变量,一旦设置了在观察数据中的getter方法就可使用了
pushTarget(this)
let value
const vm = this.vm
try {
// 调用观察数据的getter方法
// 进行依赖收集和取得观察数据的值
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
// 此时观察数据的依赖已经收集完
// 重置Dep.target=null
popTarget()
// 清除旧的deps
this.cleanupDeps()
}
return value
}
Ключевые шаги были аннотированы в приведенном выше коде.Ниже показано отношение между классами Observer и Watcher.Рисунок по-прежнему описывается нашим примером:
- Красная стрелка: создается экземпляр класса Watcher, вызывающий экземпляр Watcher.
get()
метод и наборDep.target
Для текущего экземпляра наблюдателя инициировать наблюдение за объектомgetter
метод. - синяя стрелка:
counter
объектgetter
метод срабатывает, вызовdep.depend()
Выполнить сбор зависимостей и возвратcounter
ценность . Зависит от собранных результатов:1.counter
экземпляр dep закрытияsubs
Добавьте экземпляр наблюдателя w1, который наблюдает за ним.;2. п1deps
добавить объект наблюденияcounter
отдел закрытия. - Оранжевая стрелка: когда
counter
После изменения значения срабатываетsubs
Наблюдайте за его выполнением w1 вupdate()
метод и, наконец, вызывает функцию обратного вызова cb для w1.
Другие родственные методы в классе Watcher относительно интуитивно понятны и здесь опущены.Подробности см. в исходном коде класса Watcher.
6. Dep
На рисунке выше классы Observer и Watcher связаны с Dep, так что же такое Dep?
Депа можно сравнить с издателем, Наблюдателя с читателями и Наблюдателя с книгами, связанными с Кэйго Хигасино. Например, читатель w1 интересуется «Прогулкой белой ночи» Кейго Хигасино (в нашем примере счетчик). Как только читатель w1 купит книгу Кейго Хигасино, он автоматически зарегистрируется и заполнит информацию w1 в издателе книги (пример отдела). есть последние новости о книге Кейго Хигашино (например, о скидках), w1 будет уведомлен.
Теперь посмотрим на исходный код Dep:
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 保存观察者watcher实例的数组
this.subs = []
}
// 添加观察者
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除观察者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 进行依赖收集
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()
}
}
}
Класс Dep относительно прост, и соответствующий метод также очень интуитивно понятен.Самое главное здесь — поддерживать массив, содержащий экземпляр watcher.subs
.
7. Резюме
На этом основные три категории изучены, и теперь вы можете в основном ответить на вопросы в начале статьи.
Q1:Watcher
,Dep
,Observer
Отношения между этими классами?
А1:Watcher
наблюдатель наблюдаетObserver
упакованные данные,Dep
даWatcher
Связь между данными наблюдения и основной функцией состоит в том, чтобы полагаться на сбор и уведомление об обновлениях.
Q2:Dep
серединаsubs
Что хранится?
A2: subs
Сохраняется экземпляр Watcher-наблюдателя.
Q3:Watcher
серединаdeps
Что хранится?
А3:deps
Что хранится, так это данные наблюдения в закрытииdep
пример.
Q4:Dep.target
Что это такое и где присваивается значение?
А4:Dep.target
— это глобальная переменная, которая сохраняет текущий экземпляр наблюдателя, вnew Watcher()
Когда назначение сделано, назначением является текущий экземпляр Watcher.
8. Расширение
Вот пример вычисляемого свойства:
var vue = new Vue({
el: "#app",
data: {
counter: 1
},
computed: {
result: function() {
return 'The result is :' + this.counter + 1;
}
}
})
здесьresult
Значение зависит отcounter
значение, черезresult
Это может лучше отражать адаптивные вычисления Vue. Вычисляемые свойства передаются черезinitComputed(vm, opts.computed)
После инициализации, следуя отслеживанию исходного кода, вы обнаружите, что также создается экземпляр Watcher:
watchers[key] = new Watcher(
vm, // 当前vue实例
getter || noop, // result对应的方法 function(){ return 'The result is :' + this.counter + 1;}
noop, // noop是定义的一个空方法,这里没有回调函数用noop代替
computedWatcherOptions // { lazy: true }
)
Схема показана ниже:
Это вычисляемое свойствоresult
потому что это зависит отthis.counter
, поэтому настройте наблюдателя для наблюденияresult
ценность . затем черезdefinedComputed(vm, key, userDef)
для определения вычисляемых свойств. попасть в расчетresult
, он снова сработаетthis.counter
изgetter
Метод, это делаетresult
Значение зависит отthis.counter
ценность .
Наконец, это будет вычисляемое свойствоresult
определить этоsetter/getter
Атрибуты:Object.defineProperty(target, key, sharedPropertyDefinition)
. См. исходный код для более подробной информации.