В Vue компиляция шаблона тоже очень важная часть, и тоже очень сложная.Это исследование не будет вникать в каждую деталь, а сделает общий обзор.Давай, пойдем и разберемся со мной.
Первый опыт
Мы будем знать, что когда мы смотрим на функцию инициализации Vue, на последнем шаге она выполняется__vm.$mount(el)__
операция, и этот $mount был определен в двух местах, соответственно вEntry-runtime-with-compiler.js (аббревиатура: eMount)а такжесреда выполнения/index.js (аббревиатура: rMount)В чем разница между этими двумя файлами?
// entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount // 这个 $mount 其实就是 rMount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
const options = this.$options
if (!options.render) {
...
if(template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
}
...
}
return mount.call(this, el, hydrating)
}
На самом деле, eMount по-прежнему вызывает rMount в конце, но он выполняет определенные операции в eMount. Если вы предоставите функцию рендеринга, он будет напрямую вызывать rMount. Если нет, он узнает, предоставили ли вы шаблон. при условии, что он будет использовать el для запроса dom для создания шаблона и, наконец, вернуть функцию рендеринга через компиляцию, а затем вызвать eMount.
Как видно из вышеизложенного самая важная часть это функция compileToFunctions, которая в итоге возвращает функцию рендеринга.Про эту функцию немного сложновато.Я нарисовал картинку,чтобы увидеть ее взаимосвязь,могут быть ошибки,надеюсь Героев можно указать.
Скомпилируйте в три шага
Глядя на общий процесс компиляции, мы можем обнаружить, что основная часть — это работа, выполняемая baseCompile, переданной здесь:
- parse:На первом этапе нам нужно преобразовать шаблон в абстрактное синтаксическое дерево (AST).
- optimizer:На втором этапе мы помечаем это абстрактное синтаксическое дерево статическими узлами, чтобы можно было оптимизировать процесс рендеринга.
- generateCode:Третий шаг — сгенерировать строку функции рендеринга на основе AST.
Что ж, давайте медленно посмотрим на это один за другим.
парсер
В парсере есть очень важное понятие AST, можете пойти разбираться сами.
В Vue ASTNode делится на несколько различных типов.flow/compile.jsВнутри см. изображение ниже:
Проиллюстрируем на простом примере:
<div id="demo">
<h1>Latest Vue.js Commits</h1>
<p>{{1 + 1}}</p>
</div>
Давайте подумаем, какой AST будет генерировать этот код?
Последний сгенерированный пример нашего примера, вероятно, является таким деревом, так как же Vue выполняет такой синтаксический анализ? Продолжаем искать.
В функции синтаксического анализа мы сначала определяем множество глобальных атрибутов и функций, а затем вызываем такую функцию, как parseHTML, которая также является основной функцией синтаксического анализа.Эта функция будет непрерывно анализировать шаблон, заполнять корень и, наконец, помещать корень (АСТ) Вернуться назад.
parseHTML
В этой функции самым важным является код в цикле while, и есть несколько регулярных выражений, играющих важную роль в процессе парсинга.
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
Vue использует приведенные выше регулярные выражения для сопоставления начальных и конечных тегов, имен тегов, атрибутов и т. д.
Я положил подробные заметки о том, в то время как в моемскладЗдесь вы можете пойти и посмотреть, если вам интересно.
В то время как, на самом деле, он используется постоянноhtml.indexOf('<')
Чтобы сопоставить, а затем выполнить другую обработку синтаксического анализа в соответствии с возвращенным индексом:
- __ равно 0: __ Это означает, что это один из комментариев, условных комментариев, doctype, начального тега, конечного тега
- __Больше или равно 0:__Это означает, что это текст, выражение
- __ меньше 0: __ указывает, что HTML-тег проанализирован, и может остаться некоторый текст и выражения
Функция синтаксического анализа повторяет эту работу, а затем преобразует шаблон в AST.В процессе синтаксического анализа Vue также оптимизирует пробелы между тегами, и некоторые пробелы между элементами бесполезны.
На самом деле о компиляции можно долго говорить, но здесь мы можем лишь вкратце понять идеи, а конкретный код нужно глубоко вычитать.
оптимизатор
Из комментариев в коде видно, что целью оптимизатора является поиск чисто статических поддеревьев в AST:
- Усиливайте чистый статический подметку в константы, не создавайте новые узлы каждый раз, когда вы обновляете.
- Их можно пропустить при прошивке
Объем кода оптимизации не такой большой, как у разбора, давайте посмотрим:
export function optimize (root: ?ASTElement, options: CompilerOptions) {
// 判断 root 是否存在
if (!root) return
// 判断是否是静态的属性
// 'type,tag,attrsList,attrsMap,plain,parent,children,attrs'
isStaticKey = genStaticKeysCached(options.staticKeys || '')
// 判断是否是平台保留的标签,html 或者 svg 的
isPlatformReservedTag = options.isReservedTag || no
// 第一遍遍历: 给所有静态节点打上是否是静态节点的标记
markStatic(root)
// 第二遍遍历:标记所有静态根节点
markStaticRoots(root, false)
}
Я вырезал часть следующих двух частей кода, потому что это слишком много, поэтому я не буду вставлять сюда слишком много кода, пожалуйста, обратитесь к деталям.мой склад.
первый проход
function markStatic (node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) {
...
}
}
По сути, markStatic — это рекурсивный процесс, который постоянно проверяет узлы на AST, а затем помечает их.
Как мы только что сказали, существует три типа узлов AST.В функции isStatic мы оцениваем разные типы узлов:
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression
return false
}
if (node.type === 3) { // text
return true
}
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
Вы можете видеть, что Vue обрабатывает следующие ситуации:
- Когда тип этого узла равен 2, то есть узел выражения, очевидно, что это не статический узел, поэтому он возвращает false
- Когда тип равен 3, то есть текстовый узел, он является статическим узлом и возвращает значение true.
- Если вы используете в узле элемента
v-pre
или используется<pre>
label, он добавит pre к true на этом узле, тогда это статический узел - Если это статический узел, он не должен иметь динамической привязки, директив v-if, v-for, v-else, метки слота или компонента, а не нашей пользовательской метки, родительского узла или родительского узла элемента. не может быть шаблоном с v-for, атрибуты этого узла все вtype,tag,attrsList,attrsMap,plain,parent,children,attrsВнутри, если эти условия соблюдены, он считается статическим узлом.
Далее запускаем рекурсивные операции над AST, отмечаем статические узлы, что касается того, какие операции в нем выполняются, то можете зайти на вышеуказанный склад посмотреть, здесь он не будет раскрываться.
второй проход
Второй процесс обхода состоит в том, чтобы пометить статический корневой узел, тогда каково наше определение статического корневого узла, прежде всего, корневой узел означает, что он не может быть листовым узлом, по крайней мере, он должен иметь дочерние узлы, и это статический. Здесь Vue делает пояснение: если статический узел имеет только один дочерний узел, а дочерний узел является текстовым узлом, то статическая обработка не выполняется, и ее стоимость больше, чем выгода, поэтому лучше рендерить напрямую.
Точно так же мы продолжаем рекурсивно маркировать функцию и, наконец, добавляем метку staticRoot ко всем статическим корневым узлам.Вы также можете перейти на указанный выше склад, чтобы взглянуть на этот код.
Генератор кода
В этой функции мы конвертируем AST в строку функции рендеринга, объем кода еще довольно большой, можно глянуть.
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
// 这就是编译的一些参数
const state = new CodegenState(options)
// 生成 render 字符串
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
Видно, что на финальном этапе генерации кода самой важной функцией является функция genElement.Для разных инструкций и атрибутов мы выберем разные функции генерации кода. Наконец, мы объединились в строку в соответствии с генерацией AST следующим образом:
with(this){return _c('div',{attrs:{"id":"demo"}},[(1>0)?_c('h1',[_v("Latest Vue.js Commits")]):_e(),...}
В строке функций рендера мы увидим некоторые функции, так где же эти функции определены? мы можемcore/instance/index.jsНайдите эти функции в этом файле:
// v-once
target._o = markOnce
// 转换
target._n = toNumber
target._s = toString
// v-for
target._l = renderList
// slot
target._t = renderSlot
// 是否相等
target._q = looseEqual
// 检测数组里是否有相等的值
target._i = looseIndexOf
// 渲染静态树
target._m = renderStatic
// 过滤器处理
target._f = resolveFilter
// 检查关键字
target._k = checkKeyCodes
// v-bind
target._b = bindObjectProps
// 创建文本节点
target._v = createTextVNode
// 创建空节点
target._e = createEmptyVNode
// 处理 scopeslot
target._u = resolveScopedSlots
// 处理事件绑定
target._g = bindObjectListeners
// 创建 VNode 节点
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
После компиляции мы выбираем, какую функцию обработки вызывать в соответствии с различными инструкциями, атрибутами и т. д., и, наконец, объединяем в строку функции.
Мы ясно видим, что строка рендеринга, наконец, сгенерирована, так как же нам ее использовать? На самом деле при рендеринге позже мы проводилиnew Function(render)
операция, и тогда мы можем использовать функцию рендеринга в обычном режиме.
Суммировать
Я считаю, что после большого процесса у всех будет более четкое понимание процесса компиляции, и тогда будет намного легче копаться в деталях.Чтение исходного кода на самом деле не является процессом чтения ради чтения. исходный код, мы узнаем много знаний, которые мы можем не знать в повседневной разработке.
Что касается большого куска кода в финальном генераторе кода, то я его еще не комментировал, а комментарии к исходному коду мне следует положить на склад позже, но я также считаю, что все смогут прочитать исходный код гладко.
Еще один момент, о котором следует упомянуть, это то, что в функции рендеринга Vue использует функцию with. Мы, должно быть, никогда не видели ее раньше, потому что официальный представитель не рекомендует нам использовать with. Я пошел искать причину, имея в виду эту идею, и, наконец, Я узнал, что нашел ответ You Da на форуме, этоСвязь, вы можете узнать.
В конце года я хотел бы пожелать всем счастливого нового года, и я желаю вам всего наилучшего в вашем сердце в новом году.
To Be yourself!
Напоследок словами бригадира: Мир вам и счастье!
PS: Если вы, ребята, можете смотреть это хорошо, добро пожаловать вмой складДай звезду поддержать волну, пополни.
Обратите внимание на публичный аккаунт WeChat: KnownsecFED, получайте больше качественной галантереи по коду!