首先解析 <div
,然后执行 advanceBy(context, 4)
进行截断操作(内部执行的是 s = s.slice(4)
),变成:
name=“test”>
再解析属性,并截断,变成:
同理,后面的截断情况为:
AST 节点
所有的 AST 节点定义都在 compiler-core/ast.ts 文件中,下面是一个元素节点的定义:
export interface BaseElementNode extends Node {
type: NodeTypes.ELEMENT // 类型
ns: Namespace // 命名空间 默认为 HTML,即 0
tag: string // 标签名
tagType: ElementTypes // 元素类型
isSelfClosing: boolean // 是否是自闭合标签 例如
props: Array<AttributeNode | DirectiveNode> // props 属性,包含 HTML 属性和指令
children: TemplateChildNode[] // 字节点
}
一些简单的要点已经讲完了,下面我们再从一个比较复杂的例子来详细讲解一下 parse 的处理过程。
{ { test }}
一个文本节点
上面的模板字符串假设为 s,第一个字符 s[0] 是 <
开头,那说明它只能是刚才所说的四种情况之一。这时需要再看一下 s[1] 的字符是什么:
1.如果是 !
,则调用字符串原生方法 startsWith()
看看是以 '<!--'
开头还是以 '<!DOCTYPE'
开头。虽然这两者对应的处理函数不一样,但它们最终都是解析为注释节点。2.如果是 /
,则按结束标签处理。3.如果不是 /
,则按开始标签处理。
从我们的示例来看,这是一个 <div>
开始标签。
这里还有一点要提一下,Vue 会用一个栈 stack 来保存解析到的元素标签。当它遇到开始标签时,会将这个标签推入栈,遇到结束标签时,将刚才的标签弹出栈。它的作用是保存当前已经解析了,但还没解析完的元素标签。这个栈还有另一个作用,在解析到某个字节点时,通过 stack[stack.length - 1]
可以获取它的父元素。
从我们的示例来看,它的出入栈顺序是这样的:
-
[div] // div 入栈
-
[div, p] // p 入栈
-
[div] // p 出栈
-
[div, div] // div 入栈
-
[div] // div 出栈
-
[] // 最后一个 div 出栈,模板字符串已解析完,这时栈为空
接着上文继续分析我们的示例,这时已经知道是 div
标签了,接下来会把已经解析完的 <div
字符串截断,然后解析它的属性。
Vue 的属性有两种情况:
1.HTML 普通属性2.Vue 指令
根据属性的不同生成的节点不同,HTML 普通属性节点 type 为 6,Vue 指令节点 type 为 7。
所有的节点类型值如下:
ROOT, // 根节点 0
ELEMENT, // 元素节点 1
TEXT, // 文本节点 2
COMMENT, // 注释节点 3
SIMPLE_EXPRESSION, // 表达式 4
INTERPOLATION, // 双花插值 { { }} 5
ATTRIBUTE, // 属性 6
DIRECTIVE, // 指令 7
属性解析完后,div
开始标签也就解析完了,<div name="test">
这一行字符串已经被截断。现在剩下的字符串如下:
{ { test }}
一个文本节点
注释文本和普通文本节点解析规则都很简单,直接截断,生成节点。注释文本调用 parseComment()
函数处理,文本节点调用 parseText()
处理。