Специальные инструкции
В связи с ограничением объема статьи,Интерпретация исходного кода Vue (8) — анализ компилятораРазделить на две статьи, эта правильнаяИнтерпретация исходного кода Vue (8) — анализ компилятора (часть 1)дополнение к , поэтому, пожалуйста, открывайте его, когда читаетеИнтерпретация исходного кода Vue (8) — анализ компилятора (часть 1)Читайте вместе.
processAttrs
/src/compiler/parser/index.js
/**
* 处理元素上的所有属性:
* v-bind 指令变成:el.attrs 或 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...],
* 或者是必须使用 props 的属性,变成了 el.props = [{ name, value, start, end, dynamic }, ...]
* v-on 指令变成:el.events 或 el.nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] }
* 其它指令:el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
* 原生属性:el.attrs = [{ name, value, start, end }],或者一些必须使用 props 的属性,变成了:
* el.props = [{ name, value: true, start, end, dynamic }]
*/
function processAttrs(el) {
// list = [{ name, value, start, end }, ...]
const list = el.attrsList
let i, l, name, rawName, value, modifiers, syncGen, isDynamic
for (i = 0, l = list.length; i < l; i++) {
// 属性名
name = rawName = list[i].name
// 属性值
value = list[i].value
if (dirRE.test(name)) {
// 说明该属性是一个指令
// 元素上存在指令,将元素标记动态元素
// mark element as dynamic
el.hasBindings = true
// modifiers,在属性名上解析修饰符,比如 xx.lazy
modifiers = parseModifiers(name.replace(dirRE, ''))
// support .foo shorthand syntax for the .prop modifier
if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
// 为 .props 修饰符支持 .foo 速记写法
(modifiers || (modifiers = {})).prop = true
name = `.` + name.slice(1).replace(modifierRE, '')
} else if (modifiers) {
// 属性中的修饰符去掉,得到一个干净的属性名
name = name.replace(modifierRE, '')
}
if (bindRE.test(name)) { // v-bind, <div :id="test"></div>
// 处理 v-bind 指令属性,最后得到 el.attrs 或者 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...]
// 属性名,比如:id
name = name.replace(bindRE, '')
// 属性值,比如:test
value = parseFilters(value)
// 是否为动态属性 <div :[id]="test"></div>
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
// 如果是动态属性,则去掉属性两侧的方括号 []
name = name.slice(1, -1)
}
// 提示,动态属性值不能为空字符串
if (
process.env.NODE_ENV !== 'production' &&
value.trim().length === 0
) {
warn(
`The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
)
}
// 存在修饰符
if (modifiers) {
if (modifiers.prop && !isDynamic) {
name = camelize(name)
if (name === 'innerHtml') name = 'innerHTML'
}
if (modifiers.camel && !isDynamic) {
name = camelize(name)
}
// 处理 sync 修饰符
if (modifiers.sync) {
syncGen = genAssignmentCode(value, `$event`)
if (!isDynamic) {
addHandler(
el,
`update:${camelize(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
if (hyphenate(name) !== camelize(name)) {
addHandler(
el,
`update:${hyphenate(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
}
} else {
// handler w/ dynamic event name
addHandler(
el,
`"update:"+(${name})`,
syncGen,
null,
false,
warn,
list[i],
true // dynamic
)
}
}
}
if ((modifiers && modifiers.prop) || (
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
)) {
// 将属性对象添加到 el.props 数组中,表示这些属性必须通过 props 设置
// el.props = [{ name, value, start, end, dynamic }, ...]
addProp(el, name, value, list[i], isDynamic)
} else {
// 将属性添加到 el.attrs 数组或者 el.dynamicAttrs 数组
addAttr(el, name, value, list[i], isDynamic)
}
} else if (onRE.test(name)) { // v-on, 处理事件,<div @click="test"></div>
// 属性名,即事件名
name = name.replace(onRE, '')
// 是否为动态属性
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
// 动态属性,则获取 [] 中的属性名
name = name.slice(1, -1)
}
// 处理事件属性,将属性的信息添加到 el.events 或者 el.nativeEvents 对象上,格式:
// el.events = [{ value, start, end, modifiers, dynamic }, ...]
addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
} else { // normal directives,其它的普通指令
// 得到 el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
name = name.replace(dirRE, '')
// parse arg
const argMatch = name.match(argRE)
let arg = argMatch && argMatch[1]
isDynamic = false
if (arg) {
name = name.slice(0, -(arg.length + 1))
if (dynamicArgRE.test(arg)) {
arg = arg.slice(1, -1)
isDynamic = true
}
}
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
if (process.env.NODE_ENV !== 'production' && name === 'model') {
checkForAliasModel(el, value)
}
}
} else {
// 当前属性不是指令
// literal attribute
if (process.env.NODE_ENV !== 'production') {
const res = parseText(value, delimiters)
if (res) {
warn(
`${name}="${value}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div id="{{ val }}">, use <div :id="val">.',
list[i]
)
}
}
// 将属性对象放到 el.attrs 数组中,el.attrs = [{ name, value, start, end }]
addAttr(el, name, JSON.stringify(value), list[i])
// #6887 firefox doesn't update muted state if set via attribute
// even immediately after element creation
if (!el.component &&
name === 'muted' &&
platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, 'true', list[i])
}
}
}
}
addHandler
/src/compiler/helpers.js
/**
* 处理事件属性,将事件属性添加到 el.events 对象或者 el.nativeEvents 对象中,格式:
* el.events[name] = [{ value, start, end, modifiers, dynamic }, ...]
* 其中用了大量的篇幅在处理 name 属性带修饰符 (modifier) 的情况
* @param {*} el ast 对象
* @param {*} name 属性名,即事件名
* @param {*} value 属性值,即事件回调函数名
* @param {*} modifiers 修饰符
* @param {*} important
* @param {*} warn 日志
* @param {*} range
* @param {*} dynamic 属性名是否为动态属性
*/
export function addHandler (
el: ASTElement,
name: string,
value: string,
modifiers: ?ASTModifiers,
important?: boolean,
warn?: ?Function,
range?: Range,
dynamic?: boolean
) {
// modifiers 是一个对象,如果传递的参数为空,则给一个冻结的空对象
modifiers = modifiers || emptyObject
// 提示:prevent 和 passive 修饰符不能一起使用
// warn prevent and passive modifier
/* istanbul ignore if */
if (
process.env.NODE_ENV !== 'production' && warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',
range
)
}
// 标准化 click.right 和 click.middle,它们实际上不会被真正的触发,从技术讲他们是它们
// 是特定于浏览器的,但至少目前位置只有浏览器才具有右键和中间键的点击
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (modifiers.right) {
// 右键
if (dynamic) {
// 动态属性
name = `(${name})==='click'?'contextmenu':(${name})`
} else if (name === 'click') {
// 非动态属性,name = contextmenu
name = 'contextmenu'
// 删除修饰符中的 right 属性
delete modifiers.right
}
} else if (modifiers.middle) {
// 中间键
if (dynamic) {
// 动态属性,name => mouseup 或者 ${name}
name = `(${name})==='click'?'mouseup':(${name})`
} else if (name === 'click') {
// 非动态属性,mouseup
name = 'mouseup'
}
}
/**
* 处理 capture、once、passive 这三个修饰符,通过给 name 添加不同的标记来标记这些修饰符
*/
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture
// 给带有 capture 修饰符的属性,加上 ! 标记
name = prependModifierMarker('!', name, dynamic)
}
if (modifiers.once) {
delete modifiers.once
// once 修饰符加 ~ 标记
name = prependModifierMarker('~', name, dynamic)
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive
// passive 修饰符加 & 标记
name = prependModifierMarker('&', name, dynamic)
}
let events
if (modifiers.native) {
// native 修饰符, 监听组件根元素的原生事件,将事件信息存放到 el.nativeEvents 对象中
delete modifiers.native
events = el.nativeEvents || (el.nativeEvents = {})
} else {
events = el.events || (el.events = {})
}
const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range)
if (modifiers !== emptyObject) {
// 说明有修饰符,将修饰符对象放到 newHandler 对象上
// { value, dynamic, start, end, modifiers }
newHandler.modifiers = modifiers
}
// 将配置对象放到 events[name] = [newHander, handler, ...]
const handlers = events[name]
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler)
} else if (handlers) {
events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
} else {
events[name] = newHandler
}
el.plain = false
}
addIfCondition
/src/compiler/parser/index.js
/**
* 将传递进来的条件对象放进 el.ifConditions 数组中
*/
export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
if (!el.ifConditions) {
el.ifConditions = []
}
el.ifConditions.push(condition)
}
processPre
/src/compiler/parser/index.js
/**
* 如果元素上存在 v-pre 指令,则设置 el.pre = true
*/
function processPre(el) {
if (getAndRemoveAttr(el, 'v-pre') != null) {
el.pre = true
}
}
processRawAttrs
/src/compiler/parser/index.js
/**
* 设置 el.attrs 数组对象,每个元素都是一个属性对象 { name: attrName, value: attrVal, start, end }
*/
function processRawAttrs(el) {
const list = el.attrsList
const len = list.length
if (len) {
const attrs: Array<ASTAttr> = el.attrs = new Array(len)
for (let i = 0; i < len; i++) {
attrs[i] = {
name: list[i].name,
value: JSON.stringify(list[i].value)
}
if (list[i].start != null) {
attrs[i].start = list[i].start
attrs[i].end = list[i].end
}
}
} else if (!el.pre) {
// non root node in pre blocks with no attributes
el.plain = true
}
}
processIf
/src/compiler/parser/index.js
/**
* 处理 v-if、v-else-if、v-else
* 得到 el.if = "exp",el.elseif = exp, el.else = true
* v-if 属性会额外在 el.ifConditions 数组中添加 { exp, block } 对象
*/
function processIf(el) {
// 获取 v-if 属性的值,比如 <div v-if="test"></div>
const exp = getAndRemoveAttr(el, 'v-if')
if (exp) {
// el.if = "test"
el.if = exp
// 在 el.ifConditions 数组中添加 { exp, block }
addIfCondition(el, {
exp: exp,
block: el
})
} else {
// 处理 v-else,得到 el.else = true
if (getAndRemoveAttr(el, 'v-else') != null) {
el.else = true
}
// 处理 v-else-if,得到 el.elseif = exp
const elseif = getAndRemoveAttr(el, 'v-else-if')
if (elseif) {
el.elseif = elseif
}
}
}
processOnce
/src/compiler/parser/index.js
/**
* 处理 v-once 指令,得到 el.once = true
* @param {*} el
*/
function processOnce(el) {
const once = getAndRemoveAttr(el, 'v-once')
if (once != null) {
el.once = true
}
}
checkRootConstraints
/src/compiler/parser/index.js
/**
* 检查根元素:
* 不能使用 slot 和 template 标签作为组件的根元素
* 不能在有状态组件的 根元素 上使用 v-for 指令,因为它会渲染出多个元素
* @param {*} el
*/
function checkRootConstraints(el) {
// 不能使用 slot 和 template 标签作为组件的根元素
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.',
{ start: el.start }
)
}
// 不能在有状态组件的 根元素 上使用 v-for,因为它会渲染出多个元素
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
el.rawAttrsMap['v-for']
)
}
}
closeElement
/src/compiler/parser/index.js
/**
* 主要做了 3 件事:
* 1、如果元素没有被处理过,即 el.processed 为 false,则调用 processElement 方法处理节点上的众多属性
* 2、让自己和父元素产生关系,将自己放到父元素的 children 数组中,并设置自己的 parent 属性为 currentParent
* 3、设置自己的子元素,将自己所有非插槽的子元素放到自己的 children 数组中
*/
function closeElement(element) {
// 移除节点末尾的空格,当前 pre 标签内的元素除外
trimEndingWhitespace(element)
// 当前元素不再 pre 节点内,并且也没有被处理过
if (!inVPre && !element.processed) {
// 分别处理元素节点的 key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、其它指令和一些原生属性
element = processElement(element, options)
}
// 处理根节点上存在 v-if、v-else-if、v-else 指令的情况
// 如果根节点存在 v-if 指令,则必须还提供一个具有 v-else-if 或者 v-else 的同级别节点,防止根元素不存在
// tree management
if (!stack.length && element !== root) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
if (process.env.NODE_ENV !== 'production') {
// 检查根元素
checkRootConstraints(element)
}
// 给根元素设置 ifConditions 属性,root.ifConditions = [{ exp: element.elseif, block: element }, ...]
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
// 提示,表示不应该在 根元素 上只使用 v-if,应该将 v-if、v-else-if 一起使用,保证组件只有一个根元素
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`,
{ start: element.start }
)
}
}
// 让自己和父元素产生关系
// 将自己放到父元素的 children 数组中,然后设置自己的 parent 属性为 currentParent
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else {
if (element.slotScope) {
// scoped slot
// keep it in the children list so that v-else(-if) conditions can
// find it as the prev node.
const name = element.slotTarget || '"default"'
; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
}
currentParent.children.push(element)
element.parent = currentParent
}
}
// 设置自己的子元素
// 将自己的所有非插槽的子元素设置到 element.children 数组中
// final children cleanup
// filter out scoped slots
element.children = element.children.filter(c => !(c: any).slotScope)
// remove trailing whitespace node again
trimEndingWhitespace(element)
// check pre state
if (element.pre) {
inVPre = false
}
if (platformIsPreTag(element.tag)) {
inPre = false
}
// 分别为 element 执行 model、class、style 三个模块的 postTransform 方法
// 但是 web 平台没有提供该方法
// apply post-transforms
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options)
}
}
trimEndingWhitespace
/src/compiler/parser/index.js
/**
* 删除元素中空白的文本节点,比如:<div> </div>,删除 div 元素中的空白节点,将其从元素的 children 属性中移出去
*/
function trimEndingWhitespace(el) {
if (!inPre) {
let lastNode
while (
(lastNode = el.children[el.children.length - 1]) &&
lastNode.type === 3 &&
lastNode.text === ' '
) {
el.children.pop()
}
}
}
processIfConditions
/src/compiler/parser/index.js
function processIfConditions(el, parent) {
// 找到 parent.children 中的最后一个元素节点
const prev = findPrevElement(parent.children)
if (prev && prev.if) {
addIfCondition(prev, {
exp: el.elseif,
block: el
})
} else if (process.env.NODE_ENV !== 'production') {
warn(
`v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
`used on element <${el.tag}> without corresponding v-if.`,
el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
)
}
}
findPrevElement
/src/compiler/parser/index.js
/**
* 找到 children 中的最后一个元素节点
*/
function findPrevElement(children: Array<any>): ASTElement | void {
let i = children.length
while (i--) {
if (children[i].type === 1) {
return children[i]
} else {
if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
warn(
`text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
`will be ignored.`,
children[i]
)
}
children.pop()
}
}
}
помощь
На этом синтаксическая часть компилятора закончилась, я думаю, что многие видели его в тумане, даже если он будет прочитан несколько раз, он может быть не так понятен.
Не волнуйтесь, это нормально, объем кода в компиляторе действительно относительно велик. Но сам контент не сложный, сложность в том, что он должен иметь дело со слишком многими вещами, что приводит к огромному количеству кода в этой части, соответственно, он будет казаться более сложным. Это действительно не просто, по крайней мере, я думаю, что это самая сложная и сложная часть всего фреймворка.
Вы можете просмотреть видео и статьи несколько раз, написать несколько примеров кода для облегчения отладки и написать подробные комментарии там, где вы не понимаете. Это одно и то же предложение, прочитайте его сто раз, и его значение самоочевидно.
В процессе чтения вам необходимо понять суть парсерной части компилятора: разбор HTML-подобных строковых шаблонов в AST-объекты.
Так много кода делает одну вещь, которая заключается в разборе строкового шаблона и использовании объекта AST для представления и записи всего шаблона. Поэтому, когда вы читаете, вы можете записывать объекты AST, сгенерированные в процессе синтаксического анализа, чтобы помочь чтению и пониманию, чтобы вы не были так запутаны после прочтения, а также это поможет всем понять.
Вот простая заметка, пока я читаю:
const element = {
type: 1,
tag,
attrsList: [{ name: attrName, value: attrVal, start, end }],
attrsMap: { attrName: attrVal, },
rawAttrsMap: { attrName: attrVal, type: checkbox },
// v-if
ifConditions: [{ exp, block }],
// v-for
for: iterator,
alias: 别名,
// :key
key: xx,
// ref
ref: xx,
refInFor: boolean,
// 插槽
slotTarget: slotName,
slotTargetDynamic: boolean,
slotScope: 作用域插槽的表达式,
scopeSlot: {
name: {
slotTarget: slotName,
slotTargetDynamic: boolean,
children: {
parent: container,
otherProperty,
}
},
slotScope: 作用域插槽的表达式,
},
slotName: xx,
// 动态组件
component: compName,
inlineTemplate: boolean,
// class
staticClass: className,
classBinding: xx,
// style
staticStyle: xx,
styleBinding: xx,
// attr
hasBindings: boolean,
nativeEvents: {同 evetns},
events: {
name: [{ value, dynamic, start, end, modifiers }]
},
props: [{ name, value, dynamic, start, end }],
dynamicAttrs: [同 attrs],
attrs: [{ name, value, dynamic, start, end }],
directives: [{ name, rawName, value, arg, isDynamicArg, modifiers, start, end }],
// v-pre
pre: true,
// v-once
once: true,
parent,
children: [],
plain: boolean,
}
Суммировать
-
интервьюер спросил: Кратко расскажите, что делает компилятор Vue?
отвечать:
Компилятор Vue делает три вещи:
-
Разобрать HTML-шаблон компонента в объект AST.
-
Оптимизируйте, пройдите AST, пометьте каждый узел статически, отметьте, является ли он статическим узлом, а затем дополнительно отметьте статический корневой узел, чтобы эти статические узлы можно было пропустить в последующем процессе обновления; маркировка статического корня используется для генерации На этапе функции рендеринга генерируется функция рендеринга статического корневого узла.
-
Функция рендеринга рантайма генерируется из AST, то есть функция рендеринга, о которой все говорили, на самом деле есть еще одна, массив staticRenderFns, в котором хранятся функции рендеринга всех статических узлов.
-
-
интервьюер спросил: Расскажите подробно о процессе парсинга компилятора, как он превращает шаблон строки html в объект AST?
отвечать:
-
Обход строки шаблона HTML, сопоставление "
-
Пропустите некоторые теги, которые не нужно обрабатывать, например: теги комментариев, теги условных комментариев, Doctype.
Примечание. Суть всего процесса синтаксического анализа заключается в обработке открывающих и закрывающих тегов.
-
Разобрать начальный тег
-
Получить объект, включая имя тега (tagName), все атрибуты (attrs) и положение индекса тега в строке шаблона html.
-
Далее обработайте атрибут attrs, полученный на предыдущем шаге, и приведите его к виду [{ name: attrName, value: attrVal, start: xx, end: xx }, ...]
-
Объект AST генерируется по имени тега, объекту атрибута и родительскому элементу текущего элемента, который на самом деле является обычным объектом JS, который записывает некоторую информацию об элементе в виде ключа и значения.
-
Затем обработайте некоторые инструкции начального тега, такие как v-pre, v-for, v-if, v-once, и поместите результаты обработки в объект AST.
-
После обработки сохранить объект ast в массиве стека
-
Html обрезает строку, обработка завершена, строка должна избавиться от ампутированного
-
-
Разобрать закрывающие теги
-
Если конечный тег совпадает, последний элемент берется из массива стека, который является парой с текущим совпавшим конечным тегом.
-
Снова обработайте атрибуты на начальном теге, эти атрибуты отличаются от предыдущих, таких как: ключ, ref, scopedSlot, стиль и т. д., и поместите результат обработки на объект AST элемента
ПримечаниеВ видео ошибка в этой части.Посмотрел назад и обнаружил,что нет никакой проблемы,не надо менять.Это действительно так.
-
Затем свяжите текущий элемент с родительским элементом, установите родительское свойство объекта ast текущего элемента, а затем поместите себя в дочерний массив объекта ast родительского элемента.
-
-
После окончательного обхода всей строки шаблона html верните объект ast
-
Сопутствующее видео
Интерпретация исходного кода Vue (8) — анализ компилятора
Поиск внимания
Приветствую всех, чтобы следовать за мнойСчет наггетса такжеСтанция Б, если контент полезен для вас, ставьте лайк, добавляйте в избранное + подписывайтесь
Связь
-
Интерпретация исходного кода Vue (2) — процесс инициализации Vue
-
Интерпретация исходного кода Vue (4) — асинхронное обновление
-
Интерпретация исходного кода Vue (9) — оптимизация компилятора
-
Интерпретация исходного кода Vue (10) — Функция рендеринга, сгенерированная компилятором