предисловие
На вопрос о принципе двусторонней привязки данных Vue каждый может ляпнуть: Vue внутренне передаетObject.defineProperty
Способ перехвата атрибута метода, положитьdata
Чтение и запись каждых данных в объекте преобразуется вgetter
/setter
, чтобы уведомить представление об обновлении при изменении данных. Хотя общий принцип изложен в одном предложении, его внутреннюю реализацию все же стоит исследовать, и в этой статье он будет проанализирован в простой для понимания форме.Vue
Процесс реализации принципа внутренней двусторонней привязки. а потом согласноVue
Двусторонняя привязка данных исходного кода реализована для дальнейшего закрепления и углубления понимания двусторонней привязки данных. Ниже приведен рендеринг двусторонней привязки данных, которую мы реализовали:
Я много работал, чтобы написать в течение долгого времени. Если это полезно для вас, пожалуйста, помогите вручную поставить лайк и поощрить ~
Адрес гитхаба:GitHub.com/ревматизм123/…, который обобщает все статьи в блоге автора, если вам нравится или вдохновляет, пожалуйста, помогите дать звезду ~, что также является поощрением для автора.
1. Что такое двусторонняя привязка данных MVVM
MVVM
Двусторонняя привязка данных в основном относится к следующему: изменения данных обновляют представление, а изменения представления обновляют данные, как показано на следующем рисунке:
который:
- Когда содержимое поля ввода изменяется,
Data
Данные в синхронном изменении. которыйView
=>Data
Изменение. -
Data
Когда данные в текстовом узле изменяются, содержимое текстового узла изменяется синхронно. которыйData
=>View
Изменение.
в,View
изменить обновлениеData
, который можно реализовать с помощью мониторинга событий, поэтому в этой статье в основном обсуждается, какData
изменить обновлениеView
.
Мы реализуем двустороннюю привязку данных, выполнив следующие 4 шага:
1. Реализуйте прослушивательObserver
, используемый для захвата и мониторинга всех свойств и уведомления подписчиков об изменении свойств;
2. Реализуйте подписчикаDep
, используется для сбора подписчиков, для слушателейObserver
и подписчикиWatcher
осуществлять единое управление;
3. Реализовать подписчикаWatcher
, вы можете получить уведомление об изменении свойства и выполнить соответствующий метод для обновления представления;
4. Реализовать парсерCompile
, который может анализировать соответствующие инструкции каждого узла и инициализировать данные шаблона и подписчиков.
Блок-схема вышеуказанных четырех шагов показана следующим образом:
Исходный код этого экземпляра выложен на github:GitHub.com/ревматизм123/….
Во-вторых, реализация слушателя Observer
слушательObserver
Реализация в основном относится к тому, чтобы сделать объект данных «наблюдаемым», то есть каждый раз, когда данные читаются или записываются, мы можем воспринимать, что данные были прочитаны или данные были перезаписаны. Чтобы сделать данные «наблюдаемыми»,Vue 2.0
используется в исходном кодеObject.defineProperty()
перехватить каждый атрибут данныхsetter / getter
,Object.defineProperty
метод, как определено в MDN:
Метод Object.defineProperty() напрямую определяет новое свойство объекта или изменяет существующее свойство объекта и возвращает объект.
2.1, Object.DefineProperty () синтаксис
Object.defineProperty
Синтаксис определен в MDN следующим образом:
Object.defineProperty(obj, prop, descriptor)
(1) Параметры
-
obj
Объект, для которого определяются свойства.
-
prop
Имя свойства, которое необходимо определить или изменить.
-
descriptor
Дескриптор свойства, который необходимо определить или изменить.
(2) Возвращаемое значение
Объект передан в функцию.
(3) Дескриптор атрибута
Object.defineProperty()
Определите атрибуты для объектов, которые разделены на дескрипторы данных и дескрипторы доступа, и эти две формы нельзя смешивать.
И дескрипторы данных, и дескрипторы доступа имеют следующие необязательные ключи:
configurable
тогда и только тогда, когда свойствоconfigurable
дляtrue
Когда дескриптор атрибута может быть изменен, атрибут также может быть удален из соответствующего объекта.По умолчанию ложный.
enumerable
тогда и только тогда, когда свойствоenumerable
дляtrue
, свойство может появиться в свойстве перечисления объекта.Значение по умолчанию — ложь.
Дескриптор данных имеет следующие необязательные ключевые значения:
value
Значение, соответствующее этому свойству. Может быть любым допустимым значением JavaScript (число, объект, функция и т. д.).По умолчанию не определено.
writable
тогда и только тогда, когда свойствоwritable
дляtrue
час,value
может быть изменен оператором присваивания.Значение по умолчанию — ложь.
Дескриптор доступа имеет следующие необязательные ключи:
get
данное свойствоgetter
метод, если нетgetter
тогдаundefined
. При доступе к свойству метод будет выполнен, при выполнении метода никакие параметры не передаются, но он будет передан вthis
объект (из-за наследования, здесьthis
не обязательно объект, определяющий свойство). По умолчаниюundefined
.
set
данное свойствоsetter
метод, если нетsetter
тогдаundefined
. Этот метод срабатывает при изменении значения свойства. Метод примет единственный параметр, новое значение параметра для свойства. По умолчаниюundefined
.
2.2, реализация слушателя Observer
(1) Объект буквального определения
Во-первых, давайте посмотрим.Предположим, мы определяем объект следующим образом:
let person = {
name:'tom',
age:15
}
мы можем пройтиperson.name
а такжеperson.age
прочитайте и напишите это прямоperson
соответствующее значение свойства, однако, когда этоperson
свойства считываются или изменяются без нашего ведома. Итак, как нам определить объект, который мы можем воспринимать, когда его свойства считываются и записываются?
(2) Object.defineProperty() определяет объект
Предположим, мы проходимObject.defineProperty()
для определения объекта:
let val = 'tom'
let person = {}
Object.defineProperty(person,'name',{
get(){
console.log('name属性被读取了...');
return val;
},
set(newVal){
console.log('name属性被修改了...');
val = newVal;
}
})
мы проходимobject.defineProperty()
способ датьperson
изname
свойство определяетget()
а такжеset()
Перехват, который запускается всякий раз, когда свойство читается или записываетсяget()
а такжеset()
, чтобы мы могли воспринимать, когда свойства объекта считываются и записываются. График результатов теста выглядит следующим образом:
(3) Методы улучшения
Методом шага (2),person
Объект данных уже «наблюдаем» и соответствует нашим потребностям. Однако, если есть много атрибутов объекта данных, мы устанавливаем атрибуты один за другим, и код будет очень избыточным, поэтому мы инкапсулируем следующее, чтобы сделать все атрибуты объекта данных наблюдаемыми:
/**
* 循环遍历数据对象的每个属性
*/
function observable(obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let keys = Object.keys(obj);
keys.forEach((key) => {
defineReactive(obj, key, obj[key])
})
return obj;
}
/**
* 将对象的属性用 Object.defineProperty() 进行设置
*/
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`${key}属性被读取了...`);
return val;
},
set(newVal) {
console.log(`${key}属性被修改了...`);
val = newVal;
}
})
}
С помощью описанной выше инкапсуляции метода мы можем напрямую определитьperson
:
let person = observable({
name: 'tom',
age: 15
});
определяется такperson
Оба свойства являются «наблюдаемыми».
3. Внедрение абонентской базы
3.1, шаблон проектирования "публикация-подписка"
Режим публикации-подписки также называется режимом наблюдателя, который определяет зависимость между объектами «один ко многим».При изменении состояния объекта все зависящие от него объекты будут уведомлены.
(1) Преимущества модели публикации-подписки:
- Модель публикации-подписки широко используется в асинхронном программировании, это альтернатива передаче callback-функций, например, мы можем подписаться на такие события, как error и succ запросов ajax. Используя шаблон публикации-подписки в асинхронном программировании, нам не нужно уделять слишком много внимания внутреннему состоянию объекта во время асинхронной операции, а нужно только подписаться на интересующие события.
- Модель публикации-подписки может заменить жестко запрограммированный механизм уведомления между объектами, и одному объекту больше не нужно явно вызывать интерфейс другого объекта. Паттерн публикации-подписки позволяет слабо связать два объекта, хотя подробности друг о друге неизвестны, но это не влияет на их связь друг с другом. При появлении нового подписчика код издателя не нуждается в каких-либо изменениях, аналогично, когда нужно изменить издателя, это не повлияет на предыдущих подписчиков. Вы можете свободно изменять их до тех пор, пока ранее согласованные названия событий не изменились.
(2) Жизненный пример модели публикации-подписки
Наш пример офиса продаж, чтобы проиллюстрировать публикацию - подписаться с шаблона:
Сяо Мин недавно влюбился в дом.После того, как он пришел в офис продаж, ему сказали, что дома в здании уже распроданы. К счастью, менеджер по продажам сообщил Сяо Мину, что скоро будут поздние запуски, и разработчик проходит соответствующие процедуры, и после их завершения вы можете купить его.
Но когда именно, пока никто не знает. Поэтому Сяо Мин записал номер телефона офиса продаж и будет звонить каждый день, чтобы спросить, не пора ли покупать. Помимо Xiao Ming, Xiaohong, Xiaoqiang и Xiaolong также ежедневно консультируются по этому вопросу с отделом продаж. Через неделю менеджер по продажам решил уволиться, потому что устал отвечать на 1000 звонков с одним и тем же содержанием каждый день.
Конечно, такой дурацкой компании по продажам в реальности не существует.На самом деле история такова: Перед уходом Сяо Мин оставил свой номер телефона в офисе продаж. Менеджер по продажам пообещал ему, что он отправит сообщение, чтобы уведомить Сяо Мина, как только новая недвижимость будет запущена. То же самое верно для Xiaohong, Xiaoqiang и Xiaolong. Все их номера телефонов записаны в реестре офиса продаж. Когда новый объект запускается, продавец открывает список, проходит по указанным выше номерам телефонов и отправляет текстовое сообщение. в свою очередь, чтобы сообщить им. Это реальный пример шаблона публикации-подписки.
3.2, Реализация абонентской службы
После завершения «наблюдения» за данными, то есть мы знаем, когда данные читаются или записываются, мы можем уведомить те представления, которые зависят от данных, для обновления, когда данные читаются или записываются.Для удобства нам нужно чтобы сначала собрать все зависимости, и как только данные изменятся, мы единообразно уведомим об обновлении. По сути, это упомянутый в предыдущем разделе режим «публикация-подписчик», данные меняются на «издатель», а зависимый объект — «подписчик».
Теперь нам нужно создать контейнер коллекции зависимостей, который является подписчиком сообщений.Dep
, чтобы вместить всех "подписчиков". ПодписчикDep
Он в основном отвечает за сбор подписчиков, а затем за выполнение функции обновления, соответствующей подписчикам, при изменении данных.
Создать подписчика сообщенийDep
:
function Dep () {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
С подписчиками мы потомdefineReactive
Функция трансформируется и в нее вживляется подписчик:
defineReactive: function(data, key, val) {
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter () {
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set: function setter (newVal) {
if (newVal === val) {
return;
}
val = newVal;
dep.notify();
}
});
}
С точки зрения кода мы проектируем абонентаDep
Класс, который определяет некоторые свойства и методы, важно отметить, что он имеет статическое свойствоDep.target
, который является уникальным в миреWatcher
, потому что одновременно может быть только один глобальныйWatcher
рассчитывается, помимо собственных свойствsubs
Слишком Watcher
массив .
4. Реализация наблюдателя за подписчиками
подписчикWatcher
Вы должны добавить себя на абонент во время инициализацииDep
, как его добавить? мы уже знаем слушателяObserver
Именно в функции get выполняется операция добавления подписчиков, поэтому нам нужно добавить только подписчиковWatcher
Активировать соответствующийget
Функция для выполнения операции добавления подписчиков, то как вызватьget
Функция не может быть проще, пока получено соответствующее значение атрибута, она может быть запущена Основная причина в том, что мы используемObject.defineProperty( )
мониторинг данных. Здесь нужно остановиться еще на одном подробном моменте, нам нужно толькоWatcher
Подписчики должны быть добавлены во время инициализации, поэтому требуется операция оценки, чтобы вы могли что-то сделать с подписчиком: вDep.target
Кэшируйте подписчиков вверх и вниз, успешно добавляйте их, а затем удаляйте. подписчикWatcher
Реализация выглядит следующим образом:
function Watcher(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 全局变量 订阅者 赋值
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 全局变量 订阅者 释放
return value;
}
};
подписчикWatcher
проанализируйте, как показано ниже:
подписчикWatcher
это класс, который в своем конструкторе определяет некоторые свойства:
- **vm:** объект экземпляра Vue;
- **эксп:** да
node
узлаv-model
Значения атрибутов, такие как директивы или атрибуты в интерполяционной нотации. Такие какv-model="name"
,exp
то естьname
; - **кб:** да
Watcher
Связанная функция обновления;
Когда мы переходим к созданию экземпляра рендераwatcher
Время, сначала введитеwatcher
логика конструктора, он выполнит своюthis.get()
метод, введитеget
функция, которая сначала выполнит:
Dep.target = this; // 将自己赋值为全局的订阅者
на самом деле положитьDep.target
Назначить текущий рендерwatcher
, а затем выполните:
let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
В ходе этого процессаvm
Доступ к данным выше фактически запускает объект данныхgetter
.
за стоимость объектаgetter
оба держат одинdep
, после срабатыванияgetter
будет вызван, когдаdep.depend()
метод также будет выполнятьсяthis.addSub(Dep.target)
, то есть текущийwatcher
подписаться на этого держателя данныхdep
изwatchers
, цель состоит в том, чтобы уведомить, какие данные могут быть уведомлены при изменении последующих данных.watchers
подготовиться к.
Это фактически завершило процесс сбора зависимостей. Так это здесь? На самом деле нет, после завершения сбора зависимостей нужно поставитьDep.target
Возврат к предыдущему состоянию, то есть:
Dep.target = null; // 释放自己
а такжеupdate()
Функция используется для вызова при изменении данныхWatcher
Его собственная функция обновления выполняет операцию обновления. пройти первымlet value = this.vm.data[this.exp];
Получите последние данные, затем сравните их с предыдущимиget()
Полученные старые данные сравниваются, и если они не совпадают, вызывается функция обновленияcb
обновить.
Пока что простой подписчикWatcher
Дизайн завершен.
5. Реализация парсерной компиляции
5.1, Анализ кода ключевой логики Parser Compile
через слушателяObserver
ПодписчикDep
и подписчикиWatcher
На самом деле реализован пример двусторонней привязки данных, но весь процесс не разобранdom
Узел, но напрямую исправить узел для замены данных, поэтому далее необходимо реализовать парсерCompile
сделать разбор и привязку. парсерCompile
Этапы реализации:
- Разбирать директивы шаблона, заменять данные шаблона и инициализировать представления;
- Привязать узел, соответствующий инструкции шаблона, к соответствующей функции обновления и инициализировать соответствующего подписчика;
Разберем ключевой код обработки инструкций в виде '{{variable}}' и пощупаем парсерCompile
Логика обработки кода ключа следующая:
compileText: function(node, exp) {
var self = this;
var initText = this.vm[exp]; // 获取属性值
this.updateText(node, initText); // dom 更新节点文本值
// 将这个指令初始化为一个订阅者,后续 exp 改变时,就会触发这个更新回调,从而更新视图
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
}
5.2 Простая реализация экземпляра Vue
слушатель завершенияObserver
, ПодписчикDep
,подписчикWatcher
и парсерCompile
реализации, мы можем смоделировать инициализациюVue
Пример для проверки выполнимости вышеизложенной теории. Мы инициализируем один следующим кодомVue
Пример, исходный код этого примера выложен на github:GitHub.com/ревматизм123/…, вы можете клонировать git, если вам интересно:
<body>
<div id="mvvm-app">
<input v-model="title">
<h2>{{title}}</h2>
<button v-on:click="clickBtn">数据初始化</button>
</div>
</body>
<script src="../dist/bundle.js"></script>
<script type="text/javascript">
var vm = new MVVM({
el: '#mvvm-app',
data: {
title: 'hello world'
},
methods: {
clickBtn: function (e) {
this.title = 'hello world';
}
},
});
</script>
Запустите приведенный выше пример, диаграмма эффекта выглядит следующим образом, это то же самое, что и фактический эффект привязки данных Vue!
6. Исходный код Vue — двусторонняя привязка данных
Главы со 2 по 6 выше, от слушателяObserver
, ПодписчикDep
,подписчикWatcher
и парсерCompile
реализация, завершает простойVue
Реализация экземпляра привязки данных. В этой главе мы начинаем сVue
Слушатель анализа исходного кодаObserver
, ПодписчикDep
,подписчикWatcher
реализация, чтобы помочь вам понятьVue
Как реализовать двустороннюю привязку данных в исходном коде.
6.1, реализация слушателя-наблюдателя
В этом разделе мы в основном познакомимся с реализацией слушателя Observer, ядром которого является использованиеObject.defineProperty
добавлено к даннымgetter
А сеттеры предназначены для автоматического выполнения некоторой логики при доступе к данным и записи данных.
(1) состояние инициализации
существуетVue
Фаза инициализации,_init
Когда метод выполнится, он выполнитсяinitState(vm)
метод, который определен в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 */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initState
Метод заключается в основном вprops
,methods
,data
,computed
а также wathcer
и другие свойства были инициализированы. Здесь мы сосредоточимся на анализеdata
, мы представим инициализацию других свойств в будущих статьях.
(2) данные инициализации
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
data
Основной процесс инициализации также делает две вещи, одна из которых заключается в определенииdata
Функция возвращает обход объекта черезproxy
поставить каждое значениеvm._data.xxx
представленыvm.xxx
на; другой должен позвонитьobserve
способ наблюдать за всемdata
менять, ставитьdata
Он также стал отзывчивым, и далее мы в основном представим наблюдение.
(3) наблюдать
observe
Функция состоит в том, чтобы отслеживать изменения в данных, которые определены вsrc/core/observer/index.js
середина:
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 (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
observe
Функция метода заключается в добавлении данных типа объекта, отличного от VNode.Observer
, если он был добавлен, вернуться напрямую, в противном случае создать экземплярObserver
Экземпляр объекта. Далее давайте посмотримObserver
эффект.
(4) Наблюдатель
Observer
это класс, роль которого заключается в добавлении геттеров и сеттеров к свойствам объекта для зависимостей для сбора и отправки обновлений:
xport class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have 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)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through all properties 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])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Observer
Логика конструктора очень проста, сначала создайте экземплярDep
объект,Dep
Объекты, которые мы представим в разделе 2. Далее будетvalue
Для вынесения суждений он будет вызываться для массивовobserveArray
метод, в противном случае вызовите простой объектwalk
метод. можно увидетьobserveArray
это пересечь массив и снова вызватьobserve
метод иwalk
Метод состоит в том, чтобы пройти вызов ключа объектаdefineReactive
метод, то давайте посмотрим, что делает этот метод.
(5) определитьреактивный
defineReactive
Функция состоит в том, чтобы определить отзывчивый объект и динамически добавить его к объекту.getter
а такжеsetter
, который определен вsrc/core/observer/index.js
середина:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
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
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && 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()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
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()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
defineReactive
Функция инициализируется в началеDep
экземпляр объекта, затем получитьobj
Дескриптор свойства , затем рекурсивный вызов дочерних объектовobserve
метод, который гарантирует, что независимо отobj
Насколько сложна структура, все ее подсвойства также могут стать реагирующими объектами, чтобы мы могли получить доступ или изменитьobj
Глубоко вложенное свойство в , также может вызывать геттеры и сеттеры.
6.2. Внедрение абонентского отдела
ПодписчикDep
это всеgetter
Ядро коллекции зависимостей, которое определено вsrc/core/observer/dep.js
середина:
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)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
Dep
ЯвляетсяClass
, который определяет некоторые свойства и методы, здесь следует отметить, что он имеет статическое свойствоtarget
, который является уникальным в миреWatcher
, что является очень умным решением, так как одновременно может быть только один глобальныйWatcher
рассчитывается, помимо собственных свойствsubs
СлишкомWatcher
массив .Dep
на самом деле правильноWatcher
разновидность управления,Dep
вырватьсяWatcher
Существовать в одиночестве бессмысленно.
6.3. Реализация наблюдателя за подписчиками
подписчикWatcher
Некоторые соответствующие реализации , которые определены вsrc/core/observer/watcher.js
середина
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
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// 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
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
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
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()
}
。。。。。。
}
Watcher
ЯвляетсяClass
В его конструкторе и определяет некоторыеDep
связанные свойства , где,this.deps
а также this.newDeps
Выражать Watcher
проведенный экземплярDep
массив экземпляров; в то время какthis.depIds
а также this.newDepIds
Представляяthis.deps
а также this.newDeps
из id
Задавать .
(1) Анализ процесса
Когда мы переходим к созданию экземпляра рендерераwatcher
, сначала введитеwatcher
логика конструктора, которая затем выполнит своюthis.get()
метод, введитеget
функция, которая сначала выполняет:
pushTarget(this)
на самом деле положитьDep.target
Назначить текущий рендерwatcher
И протолкнуть стек (для восстановления). Затем выполняется снова:
value = this.getter.call(vm, vm)
В это время срабатывает объект данныхgetter
.
стоимость каждого объектаgetter
оба держат одинdep
, после срабатыванияgetter
будет вызван, когдаdep.depend()
метод также будет выполнятьсяDep.target.addDep(this)
.
Только что мы упомянули это времяDep.target
был назначен для рендерингаwatcher
, затем выполните дляaddDep
метод:
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)
}
}
}
В это время будут сделаны некоторые логические суждения (чтобы гарантировать, что одни и те же данные не будут добавляться несколько раз), а затем выполненыdep.addSub(this)
, то он будет выполнятьсяthis.subs.push(sub)
, то есть текущийwatcher
подписаться на этого держателя данныхdep
изsubs
, цель состоит в том, чтобы уведомить, какие данные могут быть уведомлены при изменении последующих данных.subs
подготовиться к. так вvm._render()
В процессе все данные будут активированыgetter
, чтобы процесс сбора зависимостей был фактически завершен.
Когда мы изменим данные ответа в компоненте, он вызоветsetter
Логика последнего звонкаwatcher
серединаupdate
метод:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
здесь будетWatcher
Различные состояния , будут выполнять различную логику обновления.
6.4, Схема двусторонней привязки данных Vue
Вышеприведенное в основном анализирует ключевой код двусторонней привязки данных Vue, и его схематическая диаграмма может быть выражена следующим образом:
7. Резюме
Эта статья передает слушателюObserver
, ПодписчикDep
,подписчикWatcher
и реализация парсера, который имитирует инициализациюVue
Примеры, которые помогут вам понять основные принципы двусторонней привязки данных. Далее, изVue
Представлен исходный уровеньVue
Процесс реализации двусторонней привязки данных, понятьVue
Исходный код реализован логично, что углубляет понимание двусторонней привязки данных. Я надеюсь, что эта статья будет полезна для вас.
Я много работал, чтобы написать в течение долгого времени. Если это полезно для вас, пожалуйста, помогите вручную поставить лайк и поощрить ~
Адрес github: github.com/fengshi123/…, где собраны все статьи в блоге автора.Если вам нравится или есть вдохновение, пожалуйста, помогите поставить звездочку ~, что также является поощрением для автора.
использованная литература
1. Принцип двусторонней привязки Vue и его реализация:Блог Woo Woo.cn на.com/Go to Buddha/Afraid/68…
2. Технология Vue показала:США ТБ Huang Yi.GitHub.IO/v UE-Anarias…