theme: fancy
highlight: a11y-light
如何将这个模板<div class='red'></div >
转化为Ast,在上节基础上。
需要做的是消除空字符
以及对标签属性
的解析
消除空字符需要用到正则
const match = /^[\t\r\n\f ]+/.exec(context.source);
[\t\r\n\f ]+
: 这是一个字符类(character class),它匹配包括制表符(\t
)、回车符(\r
)、换行符(\n
)、换页符(\f
)和空格符(`)在内的空白字符,以及一个或多个这些字符。
+` 表示匹配前面的字符类至少一次或多次。
处理完空字符之后使用parseAttributes
处理标签上的属性,处理之后判断结尾是/>
还是>
,这样就可以判断是否是自闭合标签,记录在isSelfClosing
中并返回这个字段。
退出回到parseElement
函数,在这里判断isSelfClosing
,如果是true
,意味着没有children
,直接返回即可
function advanceSpaces(context) {
const match = /^[\t\r\n\f ]+/.exec(context.source);
if (match) {
advanceBy(context, match[0].length);
}
}
function parseTag(context, type) {
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source);
const tag = match[1];
advanceBy(context, match[0].length);
advanceSpaces(context);
const props=parseAttributes(context);
const isSelfClosing = context.source.startsWith("/>");
advanceBy(context, isSelfClosing ? 2 : 1);
return type === "Start"
? {
type: "Element",
tag,
isSelfClosing,
props
}
: null;
}
下面来看看parseAttributes
具体怎么实现的
以class = 'red' v-if=true >
为例
- 利用
正则
提取出class,清除等号之前的空字符 - 消除等号,再清除之前后的空字符,后面的属性有三种情况,被双引号,单引号包裹或没有引号包裹
- 如果有引号,找到对应的右引号下标,将这部分截取下来即可,再消费字符。
- 如果没有引号,直接匹配到遇到
空字符
或者<
/
为止,这样循环下去就可以获取所有的属性了
function parseAttributes(context) {
// 用来存储解析过程中产生的属性节点和指令节点
const props = [];
// 开启 while 循环,不断地消费模板内容,直至遇到标签的“结束部分”为止
while (!context.source.startsWith(">") && !context.source.startsWith("/>")) {
// 解析属性或指令
const match = /^[^\t\r\n\f />][^\t\r\n\f/>=]*/.exec(context.source);
const name = match[0];
advanceBy(context, name.length);
advanceSpaces(context);
advanceBy(context, 1);
advanceSpaces(context);
let value = "";
// 获取当前模板内容的第一个字符
const quote = context.source[0];
// 判断属性值是否被引号引用
const isQuoted = quote === '"' || quote === "'";
if (isQuoted) {
advanceBy(context, 1);
// 获取下一个引号的索引
const endQuoteIndex = context.source.indexOf(quote);
if (endQuoteIndex > -1) {
// 获取下一个引号之前的内容作为属性值
value = context.source.slice(0, endQuoteIndex);
// 消费属性值
advanceBy(context,value.length);
// 消费引号
advanceBy(context,1);
} else {
// 缺少引号错误
console.error("缺少引号");
}
} else {
// 代码运行到这里,说明属性值没有被引号引用
// 下一个空白字符之前的内容全部作为属性值
const match = /^[^\t\r\n\f >]+/.exec(context.source);
// 获取属性值
value = match[0];
// 消费属性值
advanceBy(context,value.length);
}
// 消费属性值后面的空白字符
advanceSpaces(context);
// 使用属性名称 + 属性值创建一个属性节点,添加到 props 数组中
props.push({
type: "Attribute",
name,
value,
});
}
// 将解析结果返回
return props;
}