携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情
上一篇文章提到,html-parser.ts文件在通过正则取出文本后,将其交给外部的char方法进行处理:
// 源码文件:src\compiler\parser\index.ts
let res
let child: ASTNode | undefined
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text
}
} else if (
text !== ' ' ||
!children.length ||
children[children.length - 1].text !== ' '
) {
child = {
type: 3,
text
}
}
if (child) {
if (__DEV__ && options.outputSourceRange) {
child.start = start
child.end = end
}
children.push(child)
}
可以看到,这里对传出来的文本使用了parseText方法进行处理,如得到返回值说明文本中存在动态文本,如果返回值不存在则说明是静态文本。而我们可以看到,两种文本生成的节点的不同主要体现在expression和tokens这两个属性上,也就是说,包含动态文本才会具有这两个属性。
文本解析器text-paser
那么parseText做了什么呢,我们就要去源码的text-parser.ts文件中看一下:
// 源码文件 src\compiler\parser\text-parser.ts
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
export function parseText(
text: string,
delimiters?: [string, string]
): TextParseResult | void {
//@ts-expect-error
const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
if (!tagRE.test(text)) {
return
}
const tokens: string[] = []
const rawTokens: any[] = []
let lastIndex = (tagRE.lastIndex = 0)
let match, index, tokenValue
while ((match = tagRE.exec(text))) {
index = match.index
// push text token
if (index > lastIndex) {
rawTokens.push((tokenValue = text.slice(lastIndex, index)))
tokens.push(JSON.stringify(tokenValue))
}
// tag token
const exp = parseFilters(match[1].trim())
tokens.push(`_s(${exp})`)
rawTokens.push({ '@binding': exp })
lastIndex = index + match[0].length
}
if (lastIndex < text.length) {
rawTokens.push((tokenValue = text.slice(lastIndex)))
tokens.push(JSON.stringify(tokenValue))
}
return {
expression: tokens.join('+'),
tokens: rawTokens
}
}
首先,定义一个tagRE正则表达式对动态文本进行判断,如果取不到就说明是静态文本,直接返回空给外层处理即可。这个tagRE是可以自定义的,解析时传入一对字符串作为delimiters即可,而默认情况下则是我们在模板里常用的双大括号{{和}}。
之后,则是利用正则对象Regex的match方法对传入的文本进行遍历,match方法会对符合条件的字符串进行遍历,到结尾后返回null,然后再重新开始遍历:
var text = "this is a {{dynamic}} sentence with a lot of {{dynamics}}."
var reg = /\{\{((?:.|\r?\n)+?)\}\}/g
reg.exec(text)
// ['{{dynamic}}', 'dynamic', index: 10, input: 'this is a {{dynamic}} sentence with a lot of {{dynamics}}.', groups: undefined]
reg.exec(text)
// ['{{dynamics}}', 'dynamics', index: 45, input: 'this is a {{dynamic}} sentence with a lot of {{dynamics}}.', groups: undefined]
reg.exec(text)
// null
reg.exec(text)
// ['{{dynamic}}', 'dynamic', index: 10, input: 'this is a {{dynamic}} sentence with a lot of {{dynamics}}.', groups: undefined]
因此每次取出正则匹配到的动态文本进行处理(中间有一步过滤器处理parseFilters,回头细说):
// 源码文件 src\compiler\parser\text-parser.ts
const exp = parseFilters(match[1].trim())
tokens.push(`_s(${exp})`)
rawTokens.push({ '@binding': exp })
lastIndex = index + match[0].length
每次查找都会存储查找到最后一位的索引,如果下一次查找到动态文本的索引它大,说明中间存在静态文本,进行处理:
// 源码文件 src\compiler\parser\text-parser.ts
index = match.index
// push text token
if (index > lastIndex) {
rawTokens.push((tokenValue = text.slice(lastIndex, index)))
tokens.push(JSON.stringify(tokenValue))
}
最后将rowTokens中的表达式用加号进行连接,最后将文本转化成了这样的结构。
{
expression: "this is a "+_s(dynamic)+"sentence with a lot of "+_s(dynamics),
tokens:[
"this is a ",
{'@binding': dynamic},
"sentence with a lot of ",
{'@binding': dynamics},
]
}
而这个结构将在最后render函数的生成中派上用场。
总结
利用两篇文章的篇幅,我们了解了vue对模板的解析,分别对标签、注释、文本等模板内容进行了不同方式的解析,使之生成了AST抽象代码树,下一步我们将对其进行优化(实质上就是对静态节点打标记),以及在最后的阶段生成render函数以获得虚拟DOM。