vue3源码分析二 生成ast(编译总览)

799 阅读4分钟

生成ast对象其实就是编译我们写的模板代码

就是对这段类html代码进行编译,用对象方式{...}描述模板内容,因为我们写的模板是树形结构的,所以我们生产的ast对象也是树状的

其编译的核心就是用到parseChildren这个函数

顾名思义parseChildren,编译子元素,类似这样

<div><p></p></div>

这个函数就会编译p标签。那问题来了如果是这样呢

<div><p><span></span></p></div>

p标签里面又包含了span标签,那么就会在parseChildren里面再次调用parseChildren渲染孙子节点,编译的过程其实就是递归调用parseChildren的过程,也就是编译时发现这个元素包含子元素那么就先去编译子元素,但是我们要知道当前编译到第几层了,这个函数的第三个参数就能告诉我们

ancestors是一个数组,当每次发现有子元素时就会把当前元素push到ancestors,举个例子,假设我们模板是这样的

当递归进去parseChildren六次后发现ancestors变成这样了,包含了所有父节点

需要编译模板需要模板字符串,这个字符串就放在第一个参数context里面

因为子节点会有多种类型,有普通的元素标签类型、文本类型、注释类型等,所以第二个参数mode就是区分这些类型的。 我们来看看parseChildren具体的实现,定位到源码这个位置:

一开始定义了3个变量parent、ns、nodes 前面说了ancestors是包含了所有父节点的数组,调用last

可以里看出就是获取数组最后一个值,也就是当前渲染节点的上一层节点,就是父节点

ns是渲染的类型
0代表HTML类型
1代表SVG类型
2代表MATH_ML类型

nodes初始化为一个空数组,用来存放当下面while循环生成的node 这个函数的核心就是里面的while循环,其原理就是用正则表达式切割类html的模板字符串,例如:

<div>aaa<p>bbb</p>ccc</div>

就会先正则识别出前面的div标签,然后切割成这样

aaa<p>bbb</p>ccc</div>

再渲染文本节点aaa,之后变成这样

<p>bbb</p>ccc</div>以此类推

bbb</p>ccc</div>

</p>ccc</div>

ccc</div>

</div>

直到</div>就把整个模板渲染完了 我们看看while循环里面的具体实现,源码定位到这:

首先通过isEnd判断渲染的节点是否已经结束了
然后定义了s、node
s就是渲染的模板
node就是当前一次while循环生成的子节点,可能会有多个
继续往下看,这是第一个if判断
if (mode === 0 /* DATA / || mode === 1 / RCDATA */)
首先看看mode的值代表什么
0代表普通的html标签
1代表textarea和title标签
2代表style,iframe,script,noscript标签
3代表以<![CDATA[开始的标签,xml
style,iframe,script,noscript好理解,那为什么textarea和title比较特殊呢
下面是所有的html标签

只有textarea和title是非自闭合里面不能包含子标签的(可以包含文本标签)

继续往下走
if (!context.inVPre && startsWith(s, context.options.delimiters[0]))
判断是否使用了v-pre指令并且是否已{{开头
startsWith其实内部就是调用了原生的startsWith

如果是就通过parseInterpolation处理里面的表达式
node = parseInterpolation(context, mode);
否则
else if (mode === 0 /* DATA */ && s[0] === '<')
判断第一个字符串是否等于<,是的话有下面六种情况

1:只包含’<’这个字符,报错
4:匹配到是标签开头,以parseElement处理
5:第二个字符串是‘?’,报错并当注释处理,parseBogusComment处理注释节点
其实浏览器默认也是这样处理的

6:以上都不满足则直接报错

下面看第二种情况:第二个字符为‘!’

如果以’<!--’开头则parseComment处理注释节点
如果以’ 如果以’ <![CDATA[’开头则再判断类型如果不是html则parseCDATA处理xml,否则报错并且parseBogusComment处理
以上情况都不是则直接报错,并且parseBogusComment处理

第三种情况:第二个字符为‘/’

如果只有两个字符则报错
如果第三个字符是’>’则报错并且模板往后移3位,也就是前面说的切割模板

通过slice截取再复制回去,advancePositionWithMutation函数先不管,后面会详细说下
切割后直接continue进入下一次while循环
以上情况都不是则直接报错,并且parseBogusComment处理
继续往下走

如果到这一步还没生成node,就把它当作文本节点处理,处理函数parseText
如果生成了node就push到nodes,如果是多个node则循环一下再push进去
以上就是整个while循环的过程,可以看出其实就是渲染下面几种节点:

1、表达式({{}}里面的内容)处理函数parseInterpolation

2、注释节点             处理函数parseComment

3、异常节点             处理函数parseBogusComment

4、xml节点             处理函数parseCDATA

5、标签节点             处理函数parseElement

6、文本节点             处理函数parseText

下面我们逐个过下