Vue源码解析:模板编译Parse(二)

509 阅读5分钟

在上篇文章中 ,阐述了对字符串模板的解析,但是核心的start/end/chars函数并没有解析。接下来的2~3篇文章将分析并总结以上函数。

本文将分析start与end函数

start

在解析open标签的时候执行

参数

1. tag:正在解析的标签

2. attrs: 对象数组,如

,在解析前面
的时候,生成的attrs为 [{id:"test"},{class:"class"}]

3. unary,布尔值,表示当前解析的标签是否为自闭和标签如

4.  start,字符串开始解析的位置

5. end,字符串结束解析的位置

主要逻辑

1. 寻找命名空间,一般元素是没有命名空间的,但是与svg有关的标签会有;如果父元素有命名空间,就使用父元素的命名空间

2. 创建ast节点。createASTElement(tag, attrs, currentParent),currentParent为当前标签的父标签,其实就是内部栈的此元素的上一个元素

3. 创建el.rawAttrsMap属性,将attrs由对象数组转变成对象,便于属性查找

4. 对动态绑定的属性名称进行校验,不合法的会警告

5. 对style/script标签警告

6. preTransforms函数处理

7.  对v-pre指令进行检查

8. 检查是否为pre标签

9. 如果有v-pre指令就只调用 processRawAttrs函数

10. 如果没有v-pre指令,且元素没有处理过就调用processFor,processIf,processOnce

11. 指定根元素,即,第一个解析的元素

12. 如果是非自闭和标签,将currentParent赋值为当前正在解析的元素,并将元素入栈

13. 如果是自闭和标签就调用closeElement函数

注:closeElement函数将单独提出来讲;这里的stack非parseHTML函数内部的stack

v-pre与processRawAttrs

官方文档对v-pre的说明:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

刚开始遇到v-pre的时候会标记el.pre为true,然后将全局变量inVPre为true,这样,在随后的解析中,此元素的子节点在编译的时候,inVPre都为true跳过了v-for,v-if,v-once的编译。同时,该元素的所有子元素el.plain也为true。

然后在clolseElement函数中,通过el.pre的值来变更inVpre变量的值。(后面会讲到closeElement)

processFor

官方文档给出了多种v-for的写法。这里其实都是用正则表达式去匹配的。

详见正则分析学习系列文章: https://juejin.cn/post/6887952192368541703

processIf

元素的v-if/v-else-if/v-else都会调用这个函数,处理比较简单,见下方图片注释。

processOnce

为el.once赋值为true

end

参数

1. tag:当前正在 解析的闭合标签

2. start: 字符串开始的位置

3. end:字符串结束的位置

逻辑

1. 元素出栈,即,为当前正在解析的元素

2. 调整栈长度

2. 获取当前解析元素的父元素

4. closeElement

注:这里的stack非parseHTML函数内部的stack

closeElement

colseElement函数,是最终要的函数。在start函数中,仅仅是初步建立了ast元素,处理了一v-for,v-if,v-else-if,v-else,v-once。

主要逻辑

1. 如果函数没有processed标记,就调用processElement,再次处理ast

2. 对根元素的v-if指令进行处理

3. 对元素slotScope属性进行处理,为父元素添加scopedSlots属性,值为当前解析的ast

4. 构建父子关系,即,将currentParent.children数组添加当前元素;将当前元素的parent属性指向currentParent

5. 将当前元素子ast节点中含有scopedSlots属性的节点全部过滤掉

6. postTransforms处理

对v-if/v-else-if/v-else的处理

1. 对根元素的v-if处理

    如果根元素有v-if,其子元素有v-else-if或v-else,那就为根元素添加ifConditions属性,气质为带有v-else-if或v-else的子元素数组

2. 对其他元素的v-else/v-else-if处理

processIfConditions(element, currentParent);
  1. findPrevElement函数通过parent.children数组,找到同级的带有v-if指令的元素

2)为找到元素添加ifConditions属性

对作用域插槽的处理

1\. currentParent.children.push(element)来构建父->子的关系,如果当前元素存在作用域插槽属性,就为currentParent创建scopedSlots属性,其值为当前元素

2. 但是,为了不提提前渲染作用域插槽,就不能在父元素的children中存在带有slotScope属性的元素,因此使用filter过滤

processElement

processKey

为ast节点添加key属性,同时对transition-group组件的子节点的key值进行检测

processRef

processSlotConent/processSlotOutlet

juejin.cn/post/686679…

juejin.cn/post/686792…

processComponent

对于is属性的处理,会为ast节点添加component属性

processAttrs

主要对v-bind,v-on,v-model,及其他自定义指令进行处理。处v-bind外,之前的文章都分析过了就不在多说了。

最后

1. 在模板编译的时候,最核心的是利用栈结构来保存解析的标签/ast节点,并以此来构建一颗ast树。

2. 对于作用域插槽注意的是在父ast节点添加scopedSlots对象,其值为带有slotScope属性的子ast节点,另外为了防止在父组件编译的时候就编译出作用域插槽的vnode,需要把带有slotScope属性的子ast节点从其父节点的children数组中过滤掉

3. v-pre指令的处理,利用了栈结构,巧妙地对带有v-pre元素的子元素做了处理。

4. 重头戏processAttrs,对各种指令的处理。

下一篇将分析一下chars/comment函数