Из этого раздела официально войти
VueЯдро исходного кода также является одной из трудностей построения отзывчивой системы. Этот раздел послужит введением в анализ исходного кода процесса адаптивной сборки. Он в основном разделен на две части. Первая часть предназначена для адаптивных данных.props,methods,data,computed,watherАнализ процесса инициализации, другая часть заключается в том, чтобы попытаться вручную построить базовую отзывчивую систему, исходя из сохранения концепции дизайна исходного кода. Предвосхищая эти два основных содержания, анализ конкретных деталей исходного кода будет более удобен в следующей статье.
7.1 Инициализация данных
Оглядываясь назад на предыдущий контент, мы имеемVueАнализ исходного кода начинается с инициализации, инициализация_initБудет выполнен ряд процессов, включая слияние параметров конфигурации, агент мониторинга данных и, наконец, монтирование экземпляра. И до того, как инстанс будет смонтирован, важный процесс намеренно игнорируется.инициализация данных(которыйinitState(vm)).initStateПроцесс адаптивного дизайна данных, процесс будет нацеленprops,methods,data,computedиwatchВыполните инициализационную обработку данных и преобразуйте их в реактивные объекты, а затем мы разберем каждый процесс шаг за шагом.
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
// 初始化props
if (opts.props) { initProps(vm, opts.props); }
// 初始化methods
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); }
// 初始化watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
7.2 initProps
Краткий обзорpropsиспользования родительский компонент передает данные дочернему компоненту в виде атрибутов, а дочерний компонент передаетpropsСвойства получают значения, переданные родительским компонентом.
// 父组件
<child :test="test"></child>
var vm = new Vue({
el: '#app',
data() {
return {
test: 'child'
}
}
})
// 子组件
Vue.component('child', {
template: '<div>{{test}}</div>',
props: ['test']
})
Так проанализируйpropsНам нужно проанализировать два процесса родительского компонента и дочернего компонента.Давайте сначала посмотрим на обработку переданного значения родительским компонентом. Как описано в предыдущих статьях, родительский компонент сначала выполняет компиляцию шаблона, чтобы получитьrenderфункция, которая встречает свойства дочерних компонентов во время разбора,:test=testбудет проанализирован как{ attrs: {test: test}}и как подкомпонентrenderФункция существует следующим образом:
with(){..._c('child',{attrs:{"test":test}})}
renderРазобратьVnodeпроцесс знакомстваchildТаким образом, этот дочерний узел-заполнитель используется для создания дочерних компонентов.Vnodeпроцесс, который создает подVnodeпроцедура называетсяcreateComponent, На этом этапе мы проанализировали его в главе о компонентах, а также проанализировали расширенное использование компонента и в конечном итоге вызовемnew Vnodeсоздать подVnode. И дляpropsобработка,extractPropsFromVNodeDataбудетattrsПосле того, как атрибут проверен спецификацией, результат проверки, наконец, отображается какpropsDataВходящие в виде атрибутовVnodeв конструкторе. заключить,propsОбозначение, переданное компоненту-заполнителю, будет начинаться сpropsDataформа как подкомпонентVnodeсвойства существуют. Конкретные детали будут проанализированы ниже.
// 创建子组件过程
function createComponent() {
// props校验
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
···
// 创建子组件vnode
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
}
7.2.1 Соглашения об именах для реквизита
Сначала проверьтеpropsнормативный процесс. **propsЕсть два вида результатов после компиляции, среди которыхattrsКак анализировалось ранее, он компилируется и генерируетсяrenderфункция для обработки свойств, в то время какpropsнаписано для пользователяrenderЗначение свойства функции. **Поэтому оба метода должны быть проверены одновременно.
function extractPropsFromVNodeData (data,Ctor,tag) {
// Ctor为子类构造器
···
var res = {};
// 子组件props选项
var propOptions = Ctor.options.props;
// data.attrs针对编译生成的render函数,data.props针对用户自定义的render函数
var attrs = data.attrs;
var props = data.props;
if (isDef(attrs) || isDef(props)) {
for (var key in propOptions) {
// aB 形式转成 a-b
var altKey = hyphenate(key);
{
var keyInLowerCase = key.toLowerCase();
if (
key !== keyInLowerCase &&
attrs && hasOwn(attrs, keyInLowerCase)
) {
// 警告
}
}
}
}
}
Сосредоточьтесь на обработке исходного кода в этой части,HTML нечувствителен к регистру, все браузеры интерпретируют прописные буквы как строчные, поэтому мы используемDOMВерблюжий чехол (верблюжий чехол)propsимя должно использовать его эквивалентkebab-case(имена, разделенные тире) команда вместо.который:<child :aB="test"></child>нужно записать как<child :a-b="test"></child>
7.2.2 Реквизиты адаптивных данных
просто про анализыpropsТребуются два процесса, и родительский компонент был сопряжен ранееpropsописывается, а для подкомпонентов мы передаемpropsВозможность получить значение, переданное родительским компонентом. Давайте снова посмотрим на пару подкомпонентовpropsобработка:
обработка дочерних компонентовpropsПроцесс, происходящий в родительском компоненте_updateэтап, этот этапVnodeПроцесс генерации реальных узлов, во время которого будут встречаться детиVnode, то вызоветcreateComponentДля создания дочерних компонентов. И процесс создания дочерних компонентов восходит к_initИнициализация, в это время произойдет слияние опций, дляpropsварианты, в конечном итоге будут объединены в{props: { test: { type: null }}}письма. затем позвонитinitProps, initPropsЧто делать, в двух словах, так это поместить компонентpropsДанные устанавливаются как реагирующие данные.
function initProps (vm, propsOptions) {
var propsData = vm.$options.propsData || {};
var loop = function(key) {
···
defineReactive(props,key,value,cb);
if (!(key in vm)) {
proxy(vm, "_props", key);
}
}
// 遍历props,执行loop设置为响应式数据。
for (var key in propsOptions) loop( key );
}
вproxy(vm, "_props", key);заpropsСделайте слой прокси, пользователи черезvm.XXXможно получить через проксиvm._propsзначение на . противdefineReactive, по существу используяObject.definePropertyдля данныхgetter,setterМетод переписан. Конкретный принцип см. в содержании главы о прокси данных. Во второй половине этого раздела также будет базовая реализация.
7.3 initMethods
initMethodЭтот метод не имеет ничего общего с реакцией, представленной в этом разделе, и его реализация относительно проста, в основном для обеспеченияmethodsОпределение метода должно быть функцией, а имя не может совпадать сpropsНеоднократно определенные методы в конечном итоге будут смонтированы на корневом экземпляре.
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
{
// method必须为函数形式
if (typeof methods[key] !== 'function') {
warn(
"Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
// methods方法名不能和props重复
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
// 不能以_ or $.这些Vue保留标志开头
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
// 直接挂载到实例的属性上,可以通过vm[method]访问。
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
7.4 initData
dataФункция генерируется, когда параметры инициализации объединяются, и при выполнении функции возвращаются только реальные данные, поэтомуinitDataМетод будет выполнен первым, чтобы получить компонентdataданные, и имя каждого атрибута объекта будет проверено, чтобы гарантировать, что его нельзя комбинировать сprops,methodsповторение. Последний основной методobserve,observeметод заключается вОбъекты данных отмечены как реактивные объектыи выполнять реактивную обработку для каждого свойства объекта. В то же время иpropsПрокси обрабатывается таким же образом,proxyбудетdataСделайте слой прокси, напрямую черезvm.XXXможно получить через проксиvm._dataСвойства объекта смонтированы на нем.
function initData(vm) {
var data = vm.$options.data;
// 根实例时,data是一个对象,子组件的data是一个函数,其中getData会调用函数返回data对象
data = vm._data = typeof data === 'function'? getData(data, vm): data || {};
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
// 命名不能和方法重复
if (methods && hasOwn(methods, key)) {
warn(("Method \"" + key + "\" has already been defined as a data property."),vm);
}
}
// 命名不能和props重复
if (props && hasOwn(props, key)) {
warn("The data property \"" + key + "\" is already declared as a prop. " + "Use prop default value instead.",vm);
} else if (!isReserved(key)) {
// 数据代理,用户可直接通过vm实例返回data数据
proxy(vm, "_data", key);
}
}
// observe data
observe(data, true /* asRootData */);
}
последний разговорobserve,observeКонкретное поведение заключается в добавлении неперечислимого свойства к объекту данных.__ob__, объект флага является реагирующим объектом, и получить значение атрибута каждого объекта, переписатьgetter,setterметод, чтобы каждое значение свойства было реактивными данными. Подробный код мы проанализируем позже.
7.5 initComputed
Как и в случае с методом анализа, описанным выше,initComputedдаcomputedИнициализация данных, разница заключается в следующих моментах:
-
computedМожет быть объектом или функцией, но объект должен иметьgetterметод, поэтому, еслиcomputedПроверка требуется, когда значением свойства является объект. - против
computedДля каждого свойства создайте прослушивающую зависимость, т. е. создайте экземплярwatcher,watcherОпределение , можно временно понимать как зависимость самого использования данных,watcherЭкземпляр представляет собой еще одну зависимость данных, которую необходимо отслеживать.
За исключением разницы,initComputedОн также установит для каждого свойства адаптивные данные, а также будет реагировать наcomputedИменование делается для обнаружения и предотвращенияprops,dataконфликт.
function initComputed (vm, computed) {
···
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
// computed属性为对象时,要保证有getter方法
if (getter == null) {
warn(("Getter is missing for computed property \"" + key + "\"."),vm);
}
if (!isSSR) {
// 创建computed watcher
watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions);
}
if (!(key in vm)) {
// 设置为响应式数据
defineComputed(vm, key, userDef);
} else {
// 不能和props,data命名冲突
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
очевидноVueДля использования разработчиками предоставляется множество видов данных, но после анализа выясняется, что ядром каждой обработки является преобразование данных в реагирующие данные.Как построить реагирующую систему с реагирующими данными? упоминалось ранееwatcherЧто это? Вам нужно что-нибудь еще, чтобы построить отзывчивую систему? Далее мы пытаемся реализовать минималистскую отзывчивую систему.
7.6 Минималистская адаптивная система
VueКонструкция отзывчивой системы относительно сложна, и будет сложно понять каждый процесс, непосредственно входящий в анализ и построение исходного кода.Поэтому я считаю, что, максимально сохраняя логику проектирования исходного кода, используйте наименьшие код для создания самой простой отзывчивой системы.система необходима. правильноDep,Watcher,ObserverНачальное понимание концепции также полезно для анализа деталей дизайна адаптивной системы в следующей статье.
7.6.1 Конструкция каркаса
мы начинаем сMyVueПоскольку это адаптивная к классам структура, ее конструкция подробно описываться не будет. мы моделируемVueИдеи реализации исходного кода, создание экземпляровMyVueПри передаче конфигурации опции упрощенный код имеет только одинidэлемент монтирования и объект данныхdata. Идея моделирования исходного кода, мы сначала инициализируем данные при создании экземпляра, этот шаг является адаптивной конструкцией, мы проанализируем ее позже. После инициализации данных реальныйDOMустанавливать.
var vm = new MyVue({
id: '#app',
data: {
test: 12
}
})
// myVue.js
(function(global) {
class MyVue {
constructor(options) {
this.options = options;
// 数据的初始化
this.initData(options);
let el = this.options.id;
// 实例的挂载
this.$mount(el);
}
initData(options) {
}
$mount(el) {
}
}
}(window))
7.6.2 Настройка реактивных объектов — Наблюдатель
Сначала введите классObserver, цель этого класса — превратить данные в реагирующие объекты, используяObject.definePropertyдля данныхgetter,setterметод переписан. чтение данныхgetterэтап, который мы проведемколлекция зависимостей, в модификации данныхsetterэтап, мы будемЗависимое обновление(Введение в эти два понятия следует позже). Поэтому на этапе инициализации данных мы будем использоватьObserverЭтот класс изменяет объекты данных в соответствующие объекты, которые являются основой всех процессов.
class MyVue {
initData(options) {
if(!options.data) return;
this.data = options.data;
// 将数据重置getter,setter方法
new Observer(options.data);
}
}
// Observer类的定义
class Observer {
constructor(data) {
// 实例化时执行walk方法对每个数据属性重写getter,setter方法
this.walk(data)
}
walk(obj) {
const keys = Object.keys(obj);
for(let i = 0;i< keys.length; i++) {
// Object.defineProperty的处理逻辑
defineReactive(obj, keys[i])
}
}
}
7.6.3 Сама зависимость — Наблюдатель
Мы можем понять это так, т.WatcherЭкземпляр — это зависимость.Используются ли данные при рендеринге шаблона или когда пользователь выполняет расчеты, их можно считать зависимостью, которую необходимо отслеживать.watcherОн записывает состояние этого мониторинга зависимостей и способы обновления метода работы.
// 监听的依赖
class Watcher {
constructor(expOrFn, isRenderWatcher) {
this.getter = expOrFn;
// Watcher.prototype.get的调用会进行状态的更新。
this.get();
}
get() {}
}
Итак, в какой момент времени он будет созданwatcherА как насчет обновления состояния данных? Очевидно, что рендеринг данных в реальномDOMможет быть создан, когдаwatcher.$mountЭтот процесс был представлен в предыдущей главе, и он будет проходить через генерацию шаблона.renderфункция иrenderфункция делает реальнымDOMпроцесс. Мы упростили код,updateViewсконденсировал процесс.
class MyVue {
$mount(el) {
// 直接改写innerHTML
const updateView = _ => {
let innerHtml = document.querySelector(el).innerHTML;
let key = innerHtml.match(/{(\w+)}/)[1];
document.querySelector(el).innerHTML = this.options.data[key]
}
// 创建一个渲染的依赖。
new Watcher(updateView, true)
}
}
7.6.4 Управление зависимостями — отд.
watcherЕсли понимать это как зависимость, которую нужно отслеживать для каждых данных, тоDepЭто можно понимать как управление зависимостями. Данные можно использовать как при рендеринге, так и в вычисляемых свойствах. соответствующие каждому даннымwatcherЕсть также много. И когда мы обновляем данные, как уведомить каждую зависимость, связанную с данными, которая требуетDepУправление уведомлениями сделано. И браузер может обновлять только по одномуwatcher, поэтому вам также нужен атрибут для записи текущего обновленияwatcher. иDepЭтот класс должен делать только две вещи: собирать зависимости и отправлять зависимости для обновлений.
let uid = 0;
class Dep {
constructor() {
this.id = uid++;
this.subs = []
}
// 依赖收集
depend() {
if(Dep.target) {
// Dep.target是当前的watcher,将当前的依赖推到subs中
this.subs.push(Dep.target)
}
}
// 派发更新
notify() {
const subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
// 遍历dep中的依赖,对每个依赖执行更新操作
subs[i].update();
}
}
}
Dep.target = null;
7.6.5 Процесс управления зависимостями — defineReactive
Давайте посмотрим на процесс перехвата данных. фронтObserverсоздание экземпляра в конечном итоге вызоветdefineReactiveпереписатьgetter,setterметод. Этот метод начинается с создания экземпляраDep, то есть для создания диспетчера зависимостей данных. переписываниеgetterСбор зависимостей выполняется в методе, то есть вызовdep.dependМетоды. существуетsetterФаза, после сравнения двух чисел будет вызвано зависимое обновление отправки. которыйdep.notify
const defineReactive = (obj, key) => {
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj);
let val = obj[key]
if(property && property.configurable === false) return;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
// 做依赖的收集
if(Dep.target) {
dep.depend()
}
return val
},
set(nval) {
if(nval === val) return
// 派发更新
val = nval
dep.notify();
}
})
}
оглядыватьсяwatcher, создать экземплярwatcherбудетDep.targetустановить на текущийwatcher, после выполнения функции обновления состояния,Dep.targetЗаглушка. Таким образом, при сборе зависимостей толькоDep.targetТекущийwatcher pushприбытьDepизsubsМассив подойдет. На этапе обновления дистрибутива необходимо снова обновить только состояние.
class Watcher {
constructor(expOrFn, isRenderWatcher) {
this.getter = expOrFn;
// Watcher.prototype.get的调用会进行状态的更新。
this.get();
}
get() {
// 当前执行的watcher
Dep.target = this
this.getter()
Dep.target = null;
}
update() {
this.get()
}
}
7.6.6 Результаты
Построена минималистичная отзывчивая система. При упрощении кода сохраняется идея и логика дизайна исходного кода. На основе этого шага будет легче подробно проанализировать детали реализации каждой ссылки в исходном коде.
7.7 Резюме
В этом разделе мы официально вводим введение адаптивной системы. В предыдущей главе о агентстве данных мы узналиObject.definePropertyЭто метод выполнения перехвата данных, а основой построения отзывчивой системы является перехват данных. Давайте представим его первым.VueВ процессе внутренней инициализации данных окончательный вывод заключается в том, чтоdata,computed, или другие пользовательские данные, в конечном итоге вызываяObject.definePropertyПерехват данных. В конце статьи мы построили упрощенную версию адаптивной системы, исходя из идеи и логики дизайна исходного кода. Полная функция помогает нам проанализировать и обдумать конкретные детали реализации исходного кода в следующем разделе.
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Углубленный анализ исходного кода Vue — основа компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)