Vue源码之compile之parse

537 阅读6分钟

Hello 今天是大年初三也是情人节 祝大家牛年大吉 祝天下有情人终成眷属!!!

parse结果ast长啥样

更简单的说:

可以看到,生成的 AST 是一个树状结构,每一个节点都是一个ast节点 ,除了它自身的一些属性,还维护了它的父子关系,如 parent 指向它的父节点,children 指向它的所有子节点。为什么是树形因为dom节点本来就是树形结构。

如何解析template

如上案例,我们如何去解析,首先我们看到一个闭合标签它是由开始标签和值和结束标签组成的,那么我们就可以拆分成左中右去解析也就是先匹配解析开始标签,由于开始标签内会有属性或指令,我们又会进一步对它们进行处理,接着解析值,然后解析闭合标签。那么一个标签的ast树就出来了,通过深度遍历就能解析出一整颗完整的ast树。当然这只是简简单单的描述,对于单标签又怎么去解析,对于闭合标签没写结束标签会怎么样,如何去判断是否这个标签闭合了等等细节我们慢慢来说。

开始标签

  1. parseStartTag

对于开始标签,除了标签名之外,还有⼀些标签相关的属性。函数先通过正则表达式 startTagOpen匹配到开始标签,然后定义了 match 对象,接着循环去匹配开始标签中的属性并添加到match.attrs 中,直到匹配的开始标签的闭合符结束。如果匹配到闭合符,则获取⼀元斜线符,前进到闭合符尾,并把当前索引赋值给 match.end 。

  1. handleStartTag

handleStartTag 的核心逻辑很简单,先判断开始标签是否是一元标签,类似 img、br这样,接着对 match.attrs 遍历并做了⼀些处理,对于非单标签把标签对象丢入parseHTML的satck,然后调用start函数

  1. start
  • createASTElement

通过 createASTElement 方法去创建一个 AST 元素,并添加了 namespace。可以看到,每一个 AST元素就是一个普通的 JavaScript 对象,其中, type 表示 AST 元素类型, tag 表示标签名, attrsList 表示属性列表, attrsMap 表示属性映射表, parent 表示父的 AST 元素, children 表示子 AST 元素集合。

可以看到通过start函数首先创建了ast对象,处理指令v-for,v-if,v-once(后面我们再说),非单标签把ast树节点丢入parse的stack中,否则调用closeElement(element)。至此开始标签我们就处理完毕了。

接下来判断 textEnd 是否大于等于 0 的,满足则说明到从当前位置到 textEnd 位置都是文本,并且如果 < 是纯文本中的字符,就继续找到真正的文本结束的位置,然后前进到结束的位置。再继续判断 textEnd 小于 0 的情况,则说明整个 template 解析完毕了,把剩余的 html 都赋值给了 text 。最后调用了 options.chars 回调函数,并传 text 参数。

  1. chars
  • parseText

chars函数首先判断文本是普通值还是表达式,如果是普通值往节点children中添加type为3的对象,如果是表达式要经过parseText函数正则匹配处理,最终往节点children中添加type为2,expression,

parseText 首先根据分隔符(默认是 {{}} )构造了文本匹配的正则表达式,然后再循环匹配文本,遇到普通文本就 push 到 rawTokens 和 tokens 中,如果是表达式就转换成 _s(${exp}) push到 tokens 中,以及转换成 {@binding:exp} push 到 rawTokens 中。

结束标签

  1. parseEndTag

对于结束标签,拿到倒数第一个stack比对,不一样报错,一样就调用end函数。这里我们就知道是怎么去判断是否存在闭合标签的最后调用了 options.end 回调函数。

  1. end

end函数首先从parse的stack中获取最后的ast树节点,然后删除,接着设置当前的父节点为stack最后的ast树节点。最后调用closeElement(element),这里的stack就是用来确认父子关系的。这样就保证了当遇到闭合标签的时候,可以正确地更新 stack 的长度以及currentParent 的值,这样就维护了整个 AST 树。

3.closeElement

  • processElement

processElement这里主要是获取到平台定义的transforms对element进行解析和给element设置attrs属性。

  • transforms

平台下的transformNode之class解析

平台下的transformNode之style解析

  • processAttrs
  • addAttr

closeElement函数是首先借用平台定义的transforms去解析el.attrsList的class和style属性,然后往el添加class,style等属性。接着将剩余的el.attrList遍历添加到el.attrs中去。

至此我们的parse过程就算是结束了。

processFor

对于v-for,首先从el.attrs中获取v-for然后删除,processFor 就是从元素中拿到 v-for 指令的内容,然后分别解析出for 、 alias 、 iterator1 、 iterator2 等属性的值添加到 AST 的元素上。就我们的示例 vfor="(item,index) in data" 而言,解析出的的 for 是 data , alias 是item , iterator1 是 index ,没有 iterator2 。

processIf

对于v-if,首先从el.attrsList中取到v-if并删除,然后设置el.if和el.ifConditions,对于v-else-if设置el.elseif 对于v-else设置el.else=true

processOnce

对于v-once,从el.attrsList中取到v-once并删除,然后设置el.once=true

处理 class

处理 style

总结

1.遍历html对于注释节点和文档节点直接调用advance前进

2.对于开始标签,解析标签名tagName和属性attrs,对于非单标签把标签对象丢入parseHTML的satck,前进,然后调用start函数。

3.对于文本,找到文本结束的位子截取文本,前进,然后调用chars函数。

4.对于结束标签,拿到倒数第一个stack比对,不一样报错,一样就调用end函数。

5.start函数首先创建了ast对象,处理指令v-for,v-if,v-once,非单标签把ast树节点丢入parse的stack中,否则调用closeElement(element)

6.chars函数首先判断文本是普通值还是表达式,如果是普通值往节点children中添加type为3的对象,如果是表达式要经过parseText函数处理,最终往节点children中添加type为2,expression, tokens的对象

7.end函数首先从parse的stack中获取最后的ast树节点,然后删除,接着设置当前的父节点为stack最后的ast树节点。最后调用closeElement(element)

8.closeElement函数是首先借用平台定义的transforms去解析el.attrsList的class和style属性,然后往el添加class,style等属性。接着将剩余的el.attrList遍历添加到el.attrs中去。

9.parse的stack存放的是节点ast用来捋清父子关系的,parseHTML的stack是用来匹配开闭标签的。

10.对于v-for,首先从el.attrs中获取v-for然后删除,接着parseFor解析取到的值返回 alias,for ,iterator1对象,然后赋值到el中。

11.对于v-if,首先从el.attrsList中取到v-if并删除,然后设置el.if和el.ifConditions,对于v-else-if设置el.elseif 对于v-else设置el.else=true。

12.对于v-once,从el.attrsList中取到v-once并删除,然后设置el.once=true。