「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
一、前情回顾 & 背景
上文详细讨论了创建 AST 的方法 createASTElement 方法,以及来自 options.modules 的 preTransforms 变量所代表的 preTransfromNode 方法;
createASTElement方法创建type为1,即元素的AST节点对象,包含parent、children等用于组织节点间关系的属性;preTransformsNode方法就是预处理带有v-model且动态绑定了type属性的input标签,目的是解决无论type绑定何种值,最后都能渲染除一个符合预期的input元素。
原本上一篇就想讲完所有的 options.start 方法的所有工具方法,这一番操作后,我发现了写了一通,只写完了 preTransformsNode 方法。。。本以为能很快填完这个大坑,结果没想到这个坑是越挖越大了,今天继续填坑,今天接着写 options.start 的工具方法具体的逻辑
二、processPre 方法
方法位置:src/compiler/parser/index.js -> function processPre
方法参数:element,ast 节点对象
方法作用:从 element 的 属性对象上获取 v-pre 属性值,如果值不为 null 就说明当前 ast 对象上存在 v-pre 指令,为 element.pre 设置为 true;
function processPre (el) {
if (getAndRemoveAttr(el, 'v-pre') != null) {
el.pre = true
}
}
三、getAndRemoveAttr
方法位置:src/compiler/helpers.js
方法参数:
el:AST节点对象name:要从el上获取的属性名removeFromMap:是否从map中移除
方法作用:如果属性 name 在 el.attrsMap 中存在且值不为 null,获取这个 name 对应的值,即 el.attrsMap[name],然后从 el.attrsList 数组中移除掉 name 对应的一项,并且如果 removeFromMap 为 true 时,还要从 el.attrsMap 中删除掉 name;
注意,一般情况下,即 removeFromMap 不为 true,此时只会从 el.attrsList 数组中删除,之所以这么做是为了防止 processAttrs 方法处理,是因为 attrsMap 在 ast 生成代码的时候还要用到;
export function getAndRemoveAttr (
el: ASTElement,
name: string,
removeFromMap?: boolean
): ?string {
let val
// 将属性 name 从 el.attrsList 中移除
if ((val = el.attrsMap[name]) != null) {
const list = el.attrsList
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1) // splice 从 attrList 中删除
break
}
}
}
// 如果 removeFromMap 为 true,则从 attrsMap 中删除 name
if (removeFromMap) {
delete el.attrsMap[name]
}
// 返回属性值
return val
}
四、platformIsPreTag
方法位置:该方法是通过 createCompiler(baseOptions) 时随着 baseOptions 传入的,baseOptions 中 isPreTag 方法来自 src/platforms/web/util/element.js -> function isPreTag
方法参数:tag,标签名
方法作用:判断 tag 是否为 pre,即判断当前标签是否是 <pre></pre> 标签
export const isPreTag = (tag: ?string): boolean => tag === 'pre'
五、processRawAttrs
方法位置:src/compiler/parser/index.js -> functions processRawAttrs
方法参数:el,ast 节点对象
方法作用:将 el.attrsList 复制到 el.attrs 数组上。为什么这么做呢?
这是因为 inVPre 为 true 时,说明在当前节点上使用了 v-pre 指令,而 v-pre 指令的作用就是将当前节点及其子节点都变为静态节点,即便在使用 v-pre 的节点中使用 {{}} 这种语法也不会被处理。
el.attrs 数组中的每一项都是 { name: attrName, value: attrVal, start, end } 这种形式的。此外 el.attrs 上的属性时静态属性,数据更新时这部分属性会被忽略。
processRawAttrs调用示意
// 示意 processRawAttrs 方法的调用位置,省略原始的 parse 和 parseHTML 方法中的很多细节内容
export functions parse () {
parseHTML(template, {
// options.start
start () {
// .... 省略
if (inVPre)
// inVPre 为 true 时说明当前节点上有 v-pre 指令
// processRawAttrs 方法调用示例
processRawAttrs(element)
}
}
})
}
processRawAttrs方法
function processRawAttrs (el) {
const list = el.attrsList // 原始的 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
}
}
六、processFor
方法位置:src/compiler/parser/index.js -> functions processFor
方法参数:element,ast 节点对象
方法作用:处理节点上的 v-for 指令,将解析所得到的结果保存到 element 上。v-for 的解析结果形如:
{ for: 可迭代的对象, alias: 可迭代对象条目名 }
以下代码为例:
<span v-for="item in someArr" :key="index">{{item}}</span>
<!-- someArr = ['A', 'B', 'C', 'D'] -->
可迭代对象就是 someArr,可迭代对象条目名就是 item;
export function processFor (el: ASTElement) {
let exp
// 获取 el 上的 v-for 属性的值,v-for 虽然是指令,但是在 ast 这里就是个行内属性
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
// 解析 v-for 的表达式的值:item in someArr ,
// 得到 { for: 可迭代对象, alias: 别名 }
const res = parseFor(exp)
if (res) {
// 将 res 对象上的属性复制到 el 对象上,
// el.for = "someArr"
// el.alias = "item"
extend(el, res)
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid v-for expression: ${exp}`,
el.rawAttrsMap['v-for']
)
}
}
}
6.1 parseFor
方法位置:src/compiler/parser/index.js -> parseFor
方法参数:ast 对象上 v-for 指令对应的表达式,例如 "item in someArr";
方法作用:用各种正则把 item in someArr 这种 v-for 指令的表达式解析出来,得到可迭代对象 someArr 和可迭代对象的迭代条目 item;
export function parseFor (exp: string): ?ForParseResult {
// exp 就是 v-for="item in someArr" 中 v-for 的值
// inMatch: ["item in someArr", "item", "someArr"]
const inMatch = exp.match(forAliasRE)
if (!inMatch) return
const res = {}
res.for = inMatch[2].trim()
const alias = inMatch[1].trim().replace(stripParensRE, '')
const iteratorMatch = alias.match(forIteratorRE)
if (iteratorMatch) {
res.alias = alias.replace(forIteratorRE, '').trim()
res.iterator1 = iteratorMatch[1].trim()
if (iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim()
}
} else {
res.alias = alias
}
return res
}
如下图就是 parseFor(exp) 得到的结果:
七、总结
本文接着前文讨论了 options.start 的工具方法:
-
processPre:判断元素上是否有v-pre指令,有的话设置el.pre为true; -
getAndRemoveAttr:从el.attrsMap中获取指定属性名的属性值,然后从el.attrsList数组中删除这个属性,如果传递了removeFromMap也要从attrsMap中删除掉这个属性; -
platformIsPreTag:判断是否为 pre 标签; -
processRawAttr:将元素上的属性复制到el.attrs数组中,el.attrs数组的属性均为静态属性,当数据发生变化时忽略这些属性 -
processFor: 处理v-for指令,得到{ for: 可迭代对象, alias: 可迭代对象条目名 }