В предыдущих разделах мы
new VueНачиная с создания экземпляра, он знакомит с двумя важными этапами процесса инициализации при создании экземпляра, слиянием ресурсов параметров конфигурации и основной идеей реагирующих систем — посредничеством данных. В главе о слиянии у нас естьVueУ нас есть базовое понимание стратегии слияния расширенных опций, а в главе о прокси-серверах данных у нас есть глубокое понимание значения и сценариев использования перехвата прокси-серверов. согласно сVueИдея дизайна исходного кода, процесс инициализации также будет выполнять множество операций, таких как создание ассоциаций между компонентами, инициализация центра событий, инициализация данных и создание отзывчивой системы и т. д., и, наконец, рендеринг шаблона и данных вdomузел. Если вы непосредственно проанализируете детали реализации каждого шага в порядке процесса, будет много понятий, которые трудно понять. Поэтому в этой главе мы сначала сосредоточимся на анализе понятия,Процесс рендеринга монтирования экземпляра.
3.1 Runtime Only VS Runtime + Compiler
Прежде чем мы начнем, давайте разберемсяvueЕсть две версии, основанные на исходном коде, однаruntime only(один содержит только версию среды выполнения), а другойruntime + compiler(версия, которая включает в себя как компилятор, так и среду выполнения). Единственная разница между двумя версиями заключается в том, что последняя включает в себя компилятор.
Что такое компилятор, объясняет энциклопедия Baidu:
Проще говоря, компилятор - это «один язык (обычно язык высокого уровня)» «другой язык (обычно низкоуровневый язык)» программа для перевода. Основная работа обрабатывает современный компилятор: исходный код (исходный код) → Предварительный процессор (препроцессор) → Компилятор (компилятор) → Объектный код (объектный код) → Линкер (Линкер) → Исполняемые программы (исполнители).
С точки зрения непрофессионала, компилятор — этоисходный кодпревратиться вкод объектаИнструмент. отVueС точки зрения встроенный компилятор реализуетtemplateПреобразование шаблона компилируется в исполняемый файлjavascriptфункция скрипта.
3.1.1 Runtime + Compiler
полныйVueВерсия содержит компилятор, мы можем использоватьtemplateЗаймитесь написанием шаблонов. Компилятор автоматически скомпилирует строку шаблона в код функции рендеринга, исходный кодrenderфункция.
Если вам нужно скомпилировать шаблон на стороне клиента (например, передать строку вtemplateили смонтируйте его на элемент и используйте егоDOMвнутренний HTML в качестве шаблона), вам нужна версия, включающая компилятор.
// 需要编译器的版本
new Vue({
template: '<div>{{ hi }}</div>'
})
3.1.2 Runtime Only
Содержит только код среды выполнения, которому принадлежит созданиеVueЭкземпляр, визуализация и обработкаVirtual DOMи другие функции, в основном полный код, кроме компилятора.Runtime OnlyЕсть два применимых сценария:
1. Пишем от руки в опцияхrenderФункция для определения процесса рендеринга, на этот раз не нужно включать версию компилятора, может быть полностью выполнена.
// 不需要编译器
new Vue({
render (h) {
return h('div', this.hi)
}
})
2. С помощьюvue-loaderЭтот инструмент компиляции для компиляции, когда мы используемwebpackпровестиVueВ инженерных разработках часто используютvue-loaderправильно.vueдля компиляции, хотя мы также используемtemplateтеги шаблона для написания кода, но в настоящее времяVueБольше нет необходимости использовать компилятор, отвечающий за компиляцию шаблона, и этот процесс передается подключаемому модулю.
Очевидно, что процесс компиляции приведет к определенной потере производительности и из-за добавления скомпилированного процессового кода,VueОбщий объем кода также еще более большой (версия выполнения составляет примерно на 30%, по сравнению с полной версией). Так что в реальном развитии мы должны быть какwebpackизvue-loaderТакие инструменты компилируются дляVueФаза компиляции шаблона объединена вwebpackЭто не только уменьшает размер кода рабочей среды, но и значительно повышает производительность среды выполнения, убивая двух зайцев одним выстрелом.
3.2 Основная идея монтажа экземпляра
Опираясь на приведенную выше основу, давайте вернемся к инициализации._initкод, в коде мы наблюдаемinitProxyЗатем следует ряд вызовов функций, эти функции включают в себя создание ассоциаций компонентов, инициализацию обработки событий, определение функций рендеринга, построение систем, реагирующих на данные, и т. д. Наконец, есть фрагмент кода вelЕсли он существует, экземпляр вызовет$mountСмонтируйте экземпляр.
Vue.prototype._init = function (options) {
···
// 选项合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
// 数据代理
initProxy(vm);
vm._self = vm;
initLifecycle(vm);
// 初始化事件处理
initEvents(vm);
// 定义渲染函数
initRender(vm);
// 构建响应式系统
initState(vm);
// 等等
···
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
рукойtemplateВозьмите шаблон в качестве примера, чтобы пояснить, что такое крепления.Мы перейдем к вариантамtemplateэто строка шаблона для атрибута, например<div>{{message}}</div>, и, наконец, эта строка шаблона преобразуется в настоящую через промежуточный процессDOMузел и монтировать в опцииelРендеринг представления выполняется на корневом узле делегата. Этот промежуточный процесс является процессом монтажа, который будет проанализирован далее.
VueПроцесс монтажа более сложный.Далее пройдусь поБлок-схема, анализ кодаЕсть два способа показать вам реальный процесс монтажа.
3.2.1 Блок-схема
renderфункция, преобразование функции рендерингаVirtual DOM, чтобы создать настоящий узел.
3.2.2 Анализ кода
Далее мы анализируем процесс монтирования с точки зрения кода. Кода для монтирования много, и ниже извлечена только часть кода, относящаяся к скелету.
// 内部真正实现挂载的方法
Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined;
// 调用mountComponent方法挂载
return mountComponent(this, el, hydrating)
};
// 缓存了原型上的 $mount 方法
var mount = Vue.prototype.$mount;
// 重新定义$mount,为包含编译器和不包含编译器的版本提供不同封装,最终调用的是缓存原型上的$mount方法
Vue.prototype.$mount = function (el, hydrating) {
// 获取挂载元素
el = el && query(el);
// 挂载元素不能为跟节点
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 需要编译 or 不需要编译
// render选项不存在,代表是template模板的形式,此时需要进行模板的编译过程
if (!options.render) {
···
// 使用内部编译器编译模板
}
// 无论是template模板还是手写render函数最终调用缓存的$mount方法
return mount.call(this, el, hydrating)
}
// mountComponent方法思路
function mountComponent(vm, el, hydrating) {
// 定义updateComponent方法,在watch回调时调用。
updateComponent = function () {
// render函数渲染成虚拟DOM, 虚拟DOM渲染成真实的DOM
vm._update(vm._render(), hydrating);
};
// 实例化渲染watcher
new Watcher(vm, updateComponent, noop, {})
}
Опишем основную идею процесса монтирования на языке.
- Обязательно смонтируйте
DOMэлемент, этоDOMнеобходимо убедиться, чтоhtml,bodyЭто тип следящего узла. - Мы знаем, что есть два способа рендеринга, один через
templateСтрока шаблона, другая написана от рукиrenderфункция, упомянутая ранееtemplateШаблоны необходимо компилировать во время выполнения, а последние можно использовать напрямую.renderПараметры как функции рендеринга. Поэтому в фазе монтирования будет две ветки,templateШаблон сначала будет проанализирован шаблоном и, наконец, скомпилирован вrenderФункции рендеринга участвуют в монтировании экземпляра, а рукописный вводrenderФункция может миновать стадию компиляции и напрямую вызывать смонтированный$mountметод. - против
template, он будет использоватьVueВнутренний компилятор компилирует шаблон, а шаблон строки преобразуется в абстрактное синтаксическое дерево, то естьASTдерево, и со временем превратилось в подобноеfunction(){with(){}}функцию рендеринга, о которой мы поговорим позже. - Будь то
templateШаблон или почеркrenderфункция, в конечном итоге войдетmountComponentпроцесс, на этом этапе создается экземпляр рендераwatcher,специфическийwatcherсодержания и обсудить его в другой главе. Сначала мы знаем вывод, делаяwatcherФункция обратного вызова имеет два тайминга выполнения, один выполняется во время инициализации, а другой выполняется, когдаvmФункция обратного вызова выполняется снова, когда экземпляр обнаруживает, что данные изменились. - Функция обратного вызова выполняется
updateComponentпроцесс, этот метод имеет два этапа, один из которыхvm._render, другойvm._update.vm._renderвыполнит ранее сгенерированныйrenderвизуализировать функцию и сгенерироватьVirtual Dom tree,а такжеvm._updateбудет ли этоVirtual Dom treeперевести в реальныйDOMузел.
3.3 Компиляция шаблона
Изучив первую половину статьи, мыVueПроцесс монтирования имеет примерное понимание. Здесь есть два основных процесса, которые нам нужно понять в деталях.templateкомпиляция шаблона, другойupdateComponentдетали реализации.updateComponentМы сосредоточимся на анализе в следующей главе, а оставшаяся часть этой главы будет посвящена идеям проектирования компиляции шаблонов.
(Детали реализации компилятора чрезвычайно сложны, и освоить весь процесс компиляции за короткое время нецелесообразно, и нет необходимости полностью разъяснять процесс компиляции в общем направлении. Поэтому для шаблонов анализ статьи это только попробуйте, подробнее читатели разберутся сами)
3.3.1 шаблон три письменный
templateЕсть три способа написать шаблоны, они являются:
- шаблон строки
var vm = new Vue({
el: '#app',
template: '<div>模板字符串</div>'
})
- Селектор соответствует элементу
innerHTMLшаблон
<div id="app">
<div>test1</div>
<script type="x-template" id="test">
<p>test</p>
</script>
</div>
var vm = new Vue({
el: '#app',
template: '#test'
})
-
domэлемент, соответствующий элементуinnerHTMLшаблон
<div id="app">
<div>test1</div>
<span id="test"><div class="test2">test2</div></span>
</div>
var vm = new Vue({
el: '#app',
template: document.querySelector('#test')
})
Предпосылка компиляции шаблона должна быть правильнойtemplateПроверяется достоверность строки шаблона, и три метода записи соответствуют трем разным ветвям кода.
Vue.prototype.$mount = function () {
···
if(!options.render) {
var template = options.template;
if (template) {
// 针对字符串模板和选择符匹配模板
if (typeof template === 'string') {
// 选择符匹配模板,以'#'为前缀的选择器
if (template.charAt(0) === '#') {
// 获取匹配元素的innerHTML
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
// 针对dom元素匹配
} else if (template.nodeType) {
// 获取匹配元素的innerHTML
template = template.innerHTML;
} else {
// 其他类型则判定为非法传入
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
// 如果没有传入template模板,则默认以el元素所属的根节点作为基础模板
template = getOuterHTML(el);
}
}
}
// 判断el元素是否存在
function query (el) {
if (typeof el === 'string') {
var selected = document.querySelector(el);
if (!selected) {
warn(
'Cannot find element: ' + el
);
return document.createElement('div')
}
return selected
} else {
return el
}
}
var idToTemplate = cached(function (id) {
var el = query(id);
return el && el.innerHTML
});
Примечание: Метод шаблона X-Template обычно используется для демонстраций с очень большими шаблонами или очень маленькими приложениями.Официально не рекомендуется использовать его в других ситуациях, поскольку он отделит шаблон от других определений компонента.
3.3.2 Блок-схема компиляции
vueИдея дизайна компиляции в исходном коде относительно круглая и включает в себя много логики обработки функций.В процессе реализации умение частичной функции ловко используется для извлечения основной логики обработки и компиляции элемента конфигурации.В Чтобы понять эту дизайнерскую идею, я нарисовал логические диаграммы, помогающие понять.
3.3.3 Логический анализ
Даже если есть блок-схема, логика компиляции все еще относительно неясна для понимания.Далее проанализируйте процесс выполнения каждой ссылки в сочетании с кодом.
Vue.prototype.$mount = function () {
···
if(!options.render) {
var template = options.template;
if (template) {
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
}
...
}
}
compileToFunctionsЕсть три параметра, одинtemplateШаблон, другой представляет собой скомпилированную информацию о конфигурации, и этот метод является внешним методом компиляции, пользователь может настроить информацию о конфигурации для компиляции шаблона. Последний параметрVueпример.
// 将compileToFunction方法暴露给Vue作为静态方法存在
Vue.compile = compileToFunctions;
существуетVueВ официальной документации ,Vue.compileТолько один разрешено пройтиtemplateПараметр шаблона, что означает, что пользователи не могут решить, будет ли компилировать определенное поведение? Очевидно, нет, мы оглядываемся на код, есть два варианта для конфигурации, могут быть предоставлены пользователю, пользователю необходимо только создатьVueПри прохождении вариантов изменять конфигурацию, они:
1.delimiters: Эта опция может изменить разделитель вставки простого текста, когда не передано значение,VueРазделитель по умолчанию{{}}. Если мы хотим использовать другие шаблоны, мы можем передатьdelimitersИсправлять.
2.comments: При установке наtrueсохраняется и отображается в шаблонеHTMLПримечания. По умолчанию они отбрасываются.
Обратите внимание, что поскольку эти два параметра являются конфигурациями, считываемыми в процессе компиляции полной версии, настройка этих двух параметров в версии среды выполнения недопустима.
Затем мы шаг за шагом, чтобы найтиcompileToFunctionsкорень .
Во-первых, нам нужно иметь понимание,разные платформыVueПроцесс компиляции отличается, то есть базовый метод компиляции будет отличаться для разных платформ, и параметры конфигурации на этапе компиляции также будут разными из-за разных платформ. Но дизайнер не хочет каждый раз передавать одни и те же параметры конфигурации при компиляции разных шаблонов на одной платформе. Это приводит к более сложной реализации компиляции в исходном коде.
var createCompiler = createCompilerCreator(function baseCompile (template,options) {
//把模板解析成抽象的语法树
var ast = parse(template.trim(), options);
// 配置中有代码优化选项则会对Ast语法树进行优化
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
var ref$1 = createCompiler(baseOptions);
var compile = ref$1.compile;
var compileToFunctions = ref$1.compileToFunctions;
Эта часть кода находится вVueОпределяется вводной фазой,createCompilerCreatorмимоходомbaseCompileПосле того, как функция используется в качестве параметра, она возвращает генератор компилятора, которыйcreateCompiler, С этим генератором, когда параметры конфигурации компилятораbaseOptionsПосле того, как прошли, генератор компилятора будетГенерируется компилятор под заданную конфигурацию указанной среды, а функция выполнения компиляции возвращает объектcompileToFunctions.
здесьbaseCompileЭто место, где фактически выполняется функция компиляции, то есть метод компиляции конкретной платформы, упомянутой выше. Он уже хранится в переменной памяти как параметр при инициализации исходного кода. давайте сначала посмотримbaseCompileобщий процесс.
baseCompileФункция имеет два параметра, один передается позжеtemplateШаблон, другой — это параметры конфигурации, необходимые для компиляции. Функции, реализованные функцией, следующие:
- 1. Разберите шаблон в абстрактное синтаксическое дерево, сокращенно
AST, соответствующий кодуparseчасть. - 2. Дополнительно: оптимизация
ASTсинтаксическое дерево, выполнитьoptimizeметод. - 3. По разным платформам будет
ASTСинтаксическое дерево преобразуется в функцию рендеринга, соответствующуюgenerateфункция
Давайте посмотрим поближеcreateCompilerCreatorРеализация:
function createCompilerCreator (baseCompile) {
return function createCompiler (baseOptions) {
// 内部定义compile方法
function compile (template, options) {
···
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
createCompilerCreatorФункция имеет только одну функцию, используйтечастичная функцияИдеяbaseCompileЭтот базовый скомпилированный метод кэширует и возвращает программный генератор, который при выполненииvar ref$1 = createCompiler(baseOptions);час,createCompilerбудет определять внутреннююcompileа такжеcompileToFunctionsвернуть.
мы продолжаем следитьcompileToFunctionsпроисхождение, этоcreateCompileToFunctionFnфункционировать, чтобыcompileМетод, возвращенный для параметра, затем см.createCompileToFunctionFnлогика реализации.
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (template,options,vm) {
options = extend({}, options);
···
// 缓存的作用:避免重复编译同个模板造成性能的浪费
if (cache[key]) {
return cache[key]
}
// 执行编译方法
var compiled = compile(template, options);
···
// turn code into functions
var res = {};
var fnGenErrors = [];
// 编译出的函数体字符串作为参数传递给createFunction,返回最终的render函数
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
···
return (cache[key] = res)
}
}
createCompileToFunctionFnИспользуя концепцию замыканий, скомпилированные шаблоны кэшируются,cacheПредварительно скомпилированные результаты будут сохранены, а использование кэша позволит избежать потерь производительности, вызванных повторной компиляцией.createCompileToFunctionFnв конце концовcompileToFunctionsметод возвращает.
Далее мы анализируемcompileToFunctionsлогика реализации. После оценки результатов компиляции, не использующих кеш,compileToFunctionsбудет выполнятьcompileметод, этот метод является предыдущим анализомcreateCompiler, возвращает внутреннийcompileметод, поэтому нам нужно сначала посмотреть наcompileреализация.
function createCompiler (baseOptions) {
function compile (template, options) {
var finalOptions = Object.create(baseOptions);
var errors = [];
var tips = [];
var warn = function (msg, range, tip) {
(tip ? tips : errors).push(msg);
};
// 选项合并
if (options) {
···
// 这里会将用户传递的配置和系统自带编译配置进行合并
}
finalOptions.warn = warn;
// 将剔除空格后的模板以及合并选项后的配置作为参数传递给baseCompile方法
var compiled = baseCompile(template.trim(), finalOptions);
{
detectErrors(compiled.ast, warn);
}
compiled.errors = errors;
compiled.tips = tips;
return compiled
}
return {
compile: compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
Мы виделиcompileМетод, который фактически выполняется, является основным методом компиляции, переданным при создании генератора компилятора в начале.baseCompile,baseCompileКогда он действительно выполняется, конфигурация компиляции, переданная пользователем, будет объединена с параметрами конфигурации компиляции, которые поставляются с системой, что также является сутью идеи дизайна компилятора, упомянутой в начале.
законченныйcompileвернет объект,astКак следует из названия, это абстрактное синтаксическое дерево, преобразованное в шаблон.renderнаконец генерируетсяwithутверждение,staticRenderFnsявляется статическим в виде массиваrender.
{
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
а такжеcreateCompileToFunctionFnв конечном итоге вернет два других обернутых свойстваrender, staticRenderFns, их ядроБудуwithОператоры инкапсулируются в функции выполнения.
// 编译出的函数体字符串作为参数传递给createFunction,返回最终的render函数
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}
Слишком далеко,VueС конструкторскими идеями компилятора в принципе разобрались.Когда я впервые смотрел на код, мне всегда казалось, что дизайн логики компиляции очень сложный.Проанализировав код, я обнаружил, что это гениальная идея автора.VueСуществуют разные процессы компиляции на разных платформах, иbaseOptionsВарианты будут разными, и некоторые параметры также предоставляются пользователям для настройки.Вся идея дизайна глубоко применяет идею дизайна частичной функции, а частичная функция - это приложение закрытия. Автор использует частичные функции для кэширования методов компиляции разных платформ и в то же время убирает опции, связанные с компиляцией, и слияния, эти методы стоят нашего ежедневного изучения.
Суть компиляции -parse,generateпроцесс, автор не анализировал эти два процесса, причина в том, что есть много ветвей разбора абстрактного синтаксического дерева, и его нужно лучше понять в сочетании с реальной кодовой сценой. Код этих двух частей будет упомянут снова, когда позже будет представлена глава о конкретных логических функциях.
3.4 Резюме
Содержание этого раздела состоит из двух основных частей. Во-первых, подробно описан весь процесс экземпляра на этапе монтирования. Когда мы передаем параметры для создания экземпляра, конечной целью является преобразование параметров в реальные визуальные узлы страницы. . Эта опция имеет две формы, одна из которыхtemplateСтрока шаблона передается, а другая пишется от рукиrenderФункция передается, независимо от того, какая из них, она закончится какrenderФорма функции участвует в монтировании,renderэто инкапсулированная функцияwithутверждение. Перед визуализацией реальных узлов вам необходимоrenderфункция разрешается в виртуальнуюDOM, виртуальныйDOMдаjsи настоящийDOMмост между. окончательный_updateПроцесс позволяет виртуальномуDOMРендеринг в реальные узлы. Второй блок в основном знакомит с умными идеями реализации автора в дизайне компилятора. В этом процессе активно используется концепция частичных функций, кэширование процесса компиляции и слияние параметров удаления из процесса компиляции. Эти дизайнерские концепции и идеи достойны того, чтобы наши разработчики могли учиться и учиться.
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Углубленный анализ исходного кода Vue — основа компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)