1.1 Знакомство с Vue
VueИспользование поддерживается в соответствии с официальным заявлениемCDNа такжеNPMдва пути,CDNпутьscriptпуть будет упакованvue.jsв сценарий страницы иNPMтаким образом, какwebpackилиBrowserifyКонфигурация сборщика модулей использует, чтобыnpm install vueЭто также основная форма разработки нашего приложения. С точки зрения простого анализа идей исходного кода и деталей реализации упакованныйvue.jsАнализировать и дорабатывать исходный код будет удобнее, поэтому при анализе исходного кода этой серии используется упакованныйvueсценарий,номер версииv2.6.8
1.1.1 Основное использование
Начало анализа, конечноvueОсновное использование , мы вводимvue.jsа такжеnewвзял одинVueэкземпляр и смонтировать его в#appВыше, это самое основное использование.
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
message: '选项合并'
},
})
</script>
Несмотря на то, что целью этого раздела является иллюстрацияVueНастройка опции, начиная с настройки опции, также является самым простым способом начать чтение исходного кода с нуля, но для целостности анализа и во избежание последующего появления неизвестных понятий необходимо иметь общее представлениеvueЧто каждый сделал после того, как сценарий был представлен.
1.1.2 Конструктор Vue
Упакованный исходный код соответствуетUMDканонический, этоcommonjsа такжеamdинтеграция. а такжеVueпо сути является конструктором, и он гарантирует, чтоnewЕго можно вызывать в виде экземпляра и нельзя использовать напрямую в виде функции.
(function (global, factory) {
// 遵循UMD规范
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
···
// Vue 构造函数
function Vue (options) {
// 保证了无法直接通过Vue()去调用,只能通过new的方式去创建实例
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
return Vue
})
1.1.3 Определение методов свойств прототипа
Причина, по которой Vue может адаптироваться к базовым сценариям разработки, заключается в том, что в дополнение к часто упоминаемой поддержке компонентной разработки и полной адаптивной системы также важно, чтобы он предоставлял множество возможностей.apiМетоды, будь то статические или прототипы, достаточно богаты, чтобы удовлетворить наши повседневные основные потребности разработки. Так что читайте грамотноvue-apiДокументация и точное использованиеapiМетод является предпосылкой для продвижения к профессиональному развитию. Далее, давайте посмотрим, где определены эти атрибуты метода,Обратите внимание, что в этом разделе игнорируется конкретная реализация большинства методов свойств, и эти подробные сведения будут использоваться в последующих сериях анализа..
Первый — это метод атрибута прототипа.После определения конструктора есть пять функций, которые определены для разных сценариев.VueСвойства и методы на прототипах.
// 定义Vue原型上的init方法(内部方法)
initMixin(Vue);
// 定义原型上跟数据相关的属性方法
stateMixin(Vue);
//定义原型上跟事件相关的属性方法
eventsMixin(Vue);
// 定义原型上跟生命周期相关的方法
lifecycleMixin(Vue);
// 定义渲染相关的函数
renderMixin(Vue);
Давайте посмотрим один за другим, сначалаinitMixinОпределенныйсоздание экземпляраVueкод инициализации, который будет выполнен, когда, который является внутренним методом.
function initMixin (Vue) {
Vue.prototype._init = function (options) {}
}
stateMixinМетод будет определять метод атрибута, связанный с данными, например доступ к прокси-данным, мы можем передать его экземпляру.this.$dataа такжеthis.$propsдоступ кdata,propsЗначение , а также определяет наиболее часто используемыеthis.$set,this.$delteи другие методы.
function stateMixin (Vue) {
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
// 代理了_data,_props的访问
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
// $set, $del
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
// $watch
Vue.prototype.$watch = function (expOrFn,cb,options) {};
}
eventsMixinОпределит методы, связанные с событиями, в прототипе, упомянутом в документе.vm.$on,vm.$once,vm.$off,vm.$emitЭто определено здесь.
function eventsMixin(Vue) {
// 自定义事件监听
Vue.prototype.$on = function (event, fn) {};
// 自定义事件监听,只触发一次
Vue.prototype.$once = function (event, fn) {}
// 自定义事件解绑
Vue.prototype.$off = function (event, fn) {}
// 自定义事件通知
Vue.prototype.$emit = function (event, fn) {
}
lifecycleMixin,renderMixinОба могут рассматриваться как определения методов рендеринга жизненного цикла, таких как$forceUpdateвызвать принудительное обновление экземпляра,$nextTickОтложить обратный вызов до следующего разаDOMВыполнить после цикла обновления и т. д.
// 定义跟生命周期相关的方法
function lifecycleMixin (Vue) {
Vue.prototype._update = function (vnode, hydrating) {};
Vue.prototype.$forceUpdate = function () {};
Vue.prototype.$destroy = function () {}
}
// 定义原型上跟渲染相关的方法
function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {};
// _render函数,后面会着重讲
Vue.prototype._render = function () {};
}
1.1.4 Определение методов статических свойств
Помимо методов-прототипов,Vueтакже обеспечивает богатый глобальныйapiметоды, этоinitGlobalAPIопределено в.
/* 初始化构造器的api */
function initGlobalAPI (Vue) {
// config
var configDef = {};
configDef.get = function () { return config; };
{
configDef.set = function () {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
);
};
}
// 通过Vue.config拿到配置信息
Object.defineProperty(Vue, 'config', configDef);
// 工具类不作为公共暴露的API使用
Vue.util = {
warn: warn,
extend: extend,
mergeOptions: mergeOptions,
defineReactive: defineReactive###1
};
// Vue.set = Vue.prototype.$set
Vue.set = set;
// Vue.delete = Vue.prototype.$delete
Vue.delete = del;
// Vue.nextTick = Vue.prototype.$nextTick
Vue.nextTick = nextTick;
// 2.6 explicit observable API
Vue.observable = function (obj) {
observe(obj);
return obj
};
// 构造函数的默认选项默认为components,directive,filter, _base
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
// options里的_base属性存储Vue构造器
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
// Vue.use()
initUse(Vue);
// Vue.mixin()
initMixin$1(Vue);
// 定义extend扩展子类构造器的方法
// Vue.extend()
initExtend(Vue);
// Vue.components, Vue.directive, Vue.filter
initAssetRegisters(Vue);
}
Просмотрите исходный код, чтобы сделать краткий обзор определения статических методов.
- в исходном коде
configНастроен как слой прокси, можно пройтиVue.configПолучите конфигурацию по умолчанию, и вы можете изменить значения ее атрибутов, которые могут быть изменены конфигурацией, вы можете сначала обратиться к официальной документации. - Определяет методы инструмента, используемые внутри, такие как предупреждения, слияние объектов и т. д.
- определение
set,delet,nextTickМетоды, по сути прототипы, также имеют определения этих методов. - правильно
Vue.components,Vue.directive,Vue.filterЭто параметры ресурсов по умолчанию, которые будут проанализированы позже. - определение
Vue.use()метод - определение
Vue.mixin()метод - определение
Vue.extend()метод
Теперь я верю, что у тебя естьVueНа начальном этапе анализа исходного кода нам не нужно придерживаться каждого метода и деталей реализации идей, нам нужно только иметь общее представление об общей структуре. Имея эти основы, мы начинаем вступать в основную тему этой главы.
1.2 Параметры по умолчанию для конструкторов
Вернемся к исходному примеру, в инстанцированииVue, мы передаем объект параметров конструктору для инициализации, этот объект параметров описывает поведение, которое вы хотите, например.dataОпределите реактивные данные в экземпляре, чтобыcomputedОписывает вычисляемое свойство в экземпляре сcomponentsрегистрировать компоненты и даже определять хуки жизненного цикла, которые выполняются на разных этапах. ОднакоVueСама внутренняя часть будет поставляться с некоторыми параметрами по умолчанию, эти параметры и параметры, определяемые пользователем, будут задействованы в последующих действиях.Vueинициализация экземпляра.
существуетinitGlobalAPIВ методе есть несколько строк определений параметров по умолчанию.VueВнутренние параметры по умолчанию хранятся в статикеoptionsСвойства из исходного кодаVueОн имеет четыре параметра конфигурации по умолчанию, которыеcomponent,directive, filterи возвращает свой собственный конструктор_base.
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
// 原型上创建了一个指向为空对象的options属性
Vue.options = Object.create(null);
ASSET_TYPES.forEach(function (type) {
Vue.options[type + 's'] = Object.create(null);
});
Vue.options._base = Vue;
Очевидно, что наши разработчики хорошо знакомы с этими опциями,componentsпараметр компонента, который необходимо зарегистрировать,directivesявляется директивой, требующей регистрации, иfilterОн представляет собой фильтр, который необходимо зарегистрировать. Из деталей реализации кода,Vueдляcomponentsпри условииkeepAlive,transition,transitionGroupвстроенный компонент дляdirectivesпри условииv-model,v-showвстроенные директивы, а фильтры не имеют значений по умолчанию.
// Vue内置组件
var builtInComponents = {
KeepAlive: KeepAlive
};
var platformComponents = {
Transition: Transition,
TransitionGroup: TransitionGroup
};
// Vue 内置指令,例如: v-model, v-show
var platformDirectives = {
model: directive,
show: show
}
extend(Vue.options.components, builtInComponents);
extend(Vue.options.components, platformComponents); // 扩展内置组件
extend(Vue.options.directives, platformDirectives); // 扩展内置指令
вextendМетод реализует слияние объектов, и если свойства совпадают, старые значения перезаписываются новыми значениями свойств.
// 将_from对象合并到to对象,属性相同时,则覆盖to对象的属性
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to
}
Итак, как конструктор,VueПараметры ресурсов по умолчанию настроены следующим образом:
Vue.options = {
components: {
KeepAlive: {}
Transition: {}
TransitionGroup: {}
},
directives: {
model: {inserted: ƒ, componentUpdated: ƒ}
show: {bind: ƒ, update: ƒ, unbind: ƒ}
},
filters: {}
_base
}
1.3 Проверка опций
ВведениеVueПосле вариантов, которые у нас есть, давайте оглянемся назад и создадим экземплярVueчто произошло на сцене. Из определения конструктора мы можем легко найти этот экземплярVueОсновная операция заключается в выполнении_initметод для инициализации. Операция инициализации будет настроена путем слияния параметров, инициализации жизненного цикла, инициализации центра событий и даже создания системы, реагирующей на данные. Важнейшим первым шагом является объединение вариантов. Комбинированные параметры будут смонтированы на экземпляре$optionsв свойствах. (Вы можете сначала пройтиthis.$optionsполучить доступ к окончательным вариантам)
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid
// 记录实例化多少个vue对象
vm._uid = uid$3++;
// 选项合并,将合并后的选项赋值给实例的$options属性
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor), // 返回Vue构造函数自身的配置项
options || {},
vm
);
};
}
Как видно из кода, основное внимание при слиянии опций уделяется передаче самого пользователя.optionsварианты иVueКонфигурация параметров самого конструктора объединена. Давайте посмотримmergeOptionsреализация функции.
function mergeOptions (parent,child,vm) {
{
checkComponents(child);
}
if (typeof child === 'function') {
child = child.options;
}
// props,inject,directives的校验和规范化
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
// 针对extends扩展的子类构造器
if (!child._base) {
// extends
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
// mixins
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}
var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
// 拿到各个选择指定的选项配置,如果没有则用默认的配置
var strat = strats[key] || defaultStrat;
// 执行各自的合并策略
options[key] = strat(parent[key], child[key], vm, key);
}
// console.log(options)
return options
}
**Более неуправляемый процесс объединения опций заключается в том, что вы не знаете, какие опции конфигурации были переданы пользователем, соответствуют ли эти конфигурации спецификации и соответствуют ли они требованиям объединенной конфигурации. Поэтому правила написания каждого варианта должны быть строго ограничены, в принципе пользователям не разрешается передавать варианты вне правил. **Поэтому большая часть работы заключается в проверке параметров перед их слиянием. вcomponents,prop,inject,directiveИ т.д. находится в центре внимания проверки.
1.3.1 стандартизированное испытание компонентов
Если в проекте необходимо использовать компоненты, мыvueЗарегистрируйте компонент, передав параметры компонента во время создания экземпляра. Следовательно, именование компонентов должно соответствовать многим спецификациям, например, имена компонентов нельзя использовать.htmlзарезервированные теги (например:img,p) и не может содержать недопустимых символов и т. д. Они будут вvalidateComponentNameфункция для проверки.
// components规范检查函数
function checkComponents (options) {
// 遍历components对象,对每个属性值校验。
for (var key in options.components) {
validateComponentName(key);
}
}
function validateComponentName (name) {
if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) {
// 正则判断检测是否为非法的标签,例如数字开头
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
);
}
// 不能使用Vue自身自定义的组件名,如slot, component,不能使用html的保留标签,如 h1, svg等
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
);
}
}
1.3.2 Проверка спецификации реквизита
VueОфициальная документацияpropsЕсть две формы вариантов написания, а именно
- форма массива
{ props: ['a', 'b', 'c'] }, - Форма объекта с правилами проверки
{ props: { a: { type: 'String', default: 'prop校验' } }}Из исходного кода,Обе формы в конечном итоге будут преобразованы в форму объекта.
// props规范校验
function normalizeProps (options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
// props选项数据有两种形式,一种是['a', 'b', 'c'],一种是{ a: { type: 'String', default: 'hahah' }}
// 数组
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
// 默认将数组形式的props转换为对象形式。
res[name] = { type: null };
} else {
// 规则:保证是字符串
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else {
// 非数组,非对象则判定props选项传递非法
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}
1.3.3 Проверка спецификации впрыска
provide/injectЭта пара комбинаций может использоваться реже в нашей повседневной разработке и может использоваться, когда нам нужно предоставить данные или методы в родительском компоненте для использования дочерними компонентами.provide/inject, Обратите внимание, что ключом является потомок, а не просто потомок, который отличается отpropsсценарии использования. Официально это называется внедрением зависимостей.Внедрение зависимостей позволяет потомкам компонента получать доступ к данным/методам, внедренным родителем, и потомкам не нужно знать источник данных. Важно отметить, что полагаться на предоставленные данные нельзя.
Основное использование заключается в следующем:
// 父组件
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 后代组件
var Child = {
// 数组写法
inject: ['foo'],
// 对象写法
inject: {
foo: {
from: 'foo',
default: 'bardefault'
}
}
}
injectЕсть два способа записи опций, способ массива и способ объекта, иpropsПравила проверки непротиворечивы, и окончательныйinjectпревращаются в объекты.
// inject的规范化
function normalizeInject (options, vm) {
var inject = options.inject;
if (!inject) { return }
var normalized = options.inject = {};
//数组的形式
if (Array.isArray(inject)) {
for (var i = 0; i < inject.length; i++) {
// from: 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
normalized[inject[i]] = { from: inject[i] };
}
} else if (isPlainObject(inject)) {
// 对象的处理
for (var key in inject) {
var val = inject[key];
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val };
}
} else {
// 非法规则
warn(
"Invalid value for option \"inject\": expected an Array or an Object, " +
"but got " + (toRawType(inject)) + ".",
vm
);
}
}
1.3.4 Проверка спецификации директивы
Давайте сначала посмотрим на использование параметров команды,VueПозволяет нам настраивать директиву и предоставляет пять функций ловушек.bind, inserted, update, componentUpdated, unbind, конкретное использование может относиться кОфициально - Пользовательская директиваdocument, и в дополнение к определению функций ловушек в виде объектов, официальный также предоставляет сокращение для функций, таких как:
{
directives: {
'color-swatch': function(el, binding) {
el.style.backgroundColor = binding.value
}
}
}
Функция написана наbind,updateТакое же поведение запускается в хуке и не заботится о других хуках. Это поведение является определенной функцией. Поэтому наdirectivesПри нормализации запись функций назначает поведениеbind,updateкрюк.
function normalizeDirectives (options) {
var dirs = options.directives;
if (dirs) {
for (var key in dirs) {
var def###1 = dirs[key];
// 函数简写同样会转换成对象的形式
if (typeof def###1 === 'function') {
dirs[key] = { bind: def###1, update: def###1 };
}
}
}
}
1.3.5 Кэш функций
Этот контент не имеет ничего общего с нормализацией параметров.Прочитав код вышеприведенного определения спецификации, я обнаружил, что есть фрагмент кода оптимизации функций, который стоит изучить. Он кэширует значение после каждого выполнения функции и напрямую вызывает кэшированные данные вместо повторного выполнения функции при повторном выполнении функции, тем самым улучшая производительность внешнего интерфейса.Это типичная оптимизация пространства во времени и классический частичный функция приложение.
function cached (fn) {
var cache = Object.create(null); // 创建空对象作为缓存对象
return (function cachedFn (str) {
var hit = cache[str];
return hit || (cache[str] = fn(str)) // 每次执行时缓存对象有值则不需要执行函数方法,没有则执行并缓存起来
})
}
var camelizeRE = /-(\w)/g;
// 缓存会保存每次进行驼峰转换的结果
var camelize = cached(function (str) {
// 将诸如 'a-b'的写法统一处理成驼峰写法'aB'
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
});
1.4 Конструктор подкласса
После введения проверки опций, прежде чем формально перейти к стратегии слияния, необходимо понять еще одну вещь: конструктор подкласса. Почему нам нужно упомянуть конструктор подкласса в первую очередь?
Согласно предыдущим знаниям,VueЧетыре варианта по умолчанию предусмотрены внутри, три ключевыхcomponents,directives,filter. затем, когда мы передаем конфигурацию параметров вVueДля инициализации параметры, которые необходимо объединить, кажутся только тремя ключевыми параметрами по умолчанию, так для какого сценария используется стратегия объединения параметров, созданная в исходном коде? Ответ - этот конструктор подкласса.
VueпредоставилVue.extendстатический метод, основанный на базеVueКонструктор создает «подкласс», и конфигурация параметров, переданная этим подклассом, будет объединена с конфигурацией параметров родительского класса. Вот тут-то и появляется опция слияния сцены.
Поэтому нет необходимости сначала разбираться в реализации конструктора подкласса. В следующем примере мы создаемChildПодкласс, который наследуется от родительского классаParent, и, наконец, смонтируйте подкласс в#appна элементе. наконец полученоdataЭто результат объединения вариантов.
var Parent = Vue.extend({
data() {
test: '父类',
test1: '父类1'
}
})
var Child = Parent.extend({
data() {
test: '子类',
test2: '子类1'
}
})
var vm = new Child().$mount('#app');
console.log(vm.$data);
// 结果
{
test: '子类',
test1: '父类1',
test2: '子类1'
}
Vue.extendИдея реализации очень ясна, создаваяSubКласс этого класса, прототип этого класса указывает на родительский класс, а подклассoptionsбудет с родительским классомoptionsобъединить,mergeOptionsОстальные детали будут проанализированы далее.
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {};
var Super = this;
var name = extendOptions.name || Super.options.name;
if (name) {
validateComponentName(name); // 校验子类的名称是否符合规范
}
// 创建子类构造器
var Sub = function VueComponent (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype); // 子类继承于父类
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
// 子类和父类构造器的配置选项进行合并
Sub.options = mergeOptions(
Super.options,
extendOptions
);
return Sub // 返回子类构造函数
};
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Углубленный анализ исходного кода Vue — основа компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)