前言
transform
阶段是对AST
树的升华,可以让其在generate
阶段更好的产生的渲染函数,这里我们只关心内置的transform
,用户的transfomr
不去看,内置的transform
如下:
Vue
的提供了十四个transform
,但是在浏览器平台上最多只会执行十三个,常态下只会执行十二个,有一个是兼容模式下才会执行。接下来就是看看这十三个的处理逻辑。
分析v-if/v-else-if/v-else
指令
分析这三个指令主要有三个流程,第一步是创建条件分支,首先是v-if
,除了创建分支以外,还会创建一个ifNode
,这被称为v-if
根,而v-if
的条件分支会自动的放入这个根中。
而v-else-if
和v-else
在创建分支之前,它会从自己的位置往上去找离自己最近的v-if
根,因为这两个指令必须在v-if
之后使用,如果没有找到就会报错,在这过程中,如果遇到字符串节点并且去掉空格之后长度是0的会将其移除。
在找到之后需要做一个检验,因为v-else
可以在v-else-if
之后也可以在v-if
之后,但是v-else-if
必须要在v-if
,所以如果v-if
根中的条件分支的最后一个不存在条件表达式(v-else
不需要条件表达式),说明是v-if
后面跟着v-else
了,不能再跟着v-else-if
,会直接报错。
前面的校验通过之后,会将v-else-if
或v-else
在AST树中的node
移动到v-if
根中,其实是先移除自己在AST树中的节点,再创建条件分支,在添加进v-if
根中之前会判断分支的key
是否重复,重复报错。
第二个流程是去执行processCodegen
,这倒是非常简单,就是给多个相同等级添加不同的key
,而后返回一个退出函数。
第三个流程就是去执行processCodegen
返回的退出函数,去创建属于分支的codegen
,退出函数的执行时机是要处理完分支里面的子元素。
v-if
的codegen
是正常的在v-if
根上,而v-else-if
或v-else
则是放在v-if
的codegen
上,如果三个都存在,那么存放的关系是:v-else
放在v-else-if
上,v-else-if
放在v-if
上。所以v-if
是直接去创建codegen
,其他两个在创建codegen
的时候,需要去拿到自己前面的codegen
。
进入createCodegenNodeForBranch
中,v-if
和v-else-if
带条件走if
,而v-else
走else
。前面也说到了,三个指令的codegen
节点的存放关系,所以v-if
和v-else-if
需要产生一个条件表达式对象,v-else
不需要。v-if/v-else-if/v-else
指令分析完毕。
分析v-for
指令
v-for
的语义分析和v-if
的流程差不多,主要是靠transformFor
,里面是去调用processFor
,我们先去看它的实现。
确认v-for
指令的表达式存在,执行parseForExpression
去解析它。
首先我们要知道几个正则表达式,假设我们的表达式长这样:(item, name, index) in|of list
,
forAliasRE
会匹配出三部分,分别是:(item, name, index)
、in|of
、list
stripParensRE
匹配是 (item, name, index)
中的括号,得出里面的内容item, name, index
forIteratorRE
匹配item, name, index
中的 ,name
和,index
进入parseForExpression
,会先用forAliasRE
匹配出前半部和后半部,后半部也就是list
,会产生一个srouce
对象。放到result
中(这是表达式解析结果的汇总)
这里是要拿到(item, name, index)
中name
和index
,这两个东西是可选参数,这里匹配是否存在,存在才会进入它们两个的处理流程。匹配结果interatorMatch[1]
是name
, interatorMatch[2]
是index
。
处理name
,获取name
在表达式中的位置,通过createAliasExpression
创建别名表达式对象。放到result
中。
处理index
和item
,和处理name
一样,这里不再说明。 都是放入result
中,最后把result
返回回去。
回到processFor
中,通过parseForExpression
返回的result
创建forNode
。和ifNode
一样,会替换AST树中的自身。之后去执行processCodegen
。
函数开始,拿到和创建一系列东西,为后面的流程和退出函数做准备。
创建forNode
的codegen
,返回退出函数,在processFor
中也返回一退出函数,在后面合适的时机执行。
这里我们直接跳到执行退出函数的时候,processFor
的退出函数中会scopes
减一,表示一个vFor
处理完成。去执行prcessCodegen
的退出函数。
childrenBlock
是后面用来保存codegen
的,slotOutlet
是用来存储找到的插槽出口,没有就是null
,needFragmentWapper
和vIf
中的一样。
处理的流程主要分为两大类,插槽出口和普通元素,如果是插槽出口,需要判断v-for
是不是在<slot />
上,如果在<slot />
上,只需要注入key
即可。如果是<template v-for><slot /></template
,那么就会是有多个文本或者元素,需要为每一个children产生一个Fragment
。
如果是普通元素,那就可以直接使用children
的codegen
,但是需要把它们标记为块。
最后因为v-for
是使用renderList
渲染,需要处理一下参数,但可能会带有v-memo
指令,也需要做额外的处理。如果带有v-memo
指令,则需要加入一些复合表达式对象。比如(renderList
)函数参数、缓存校验等。
没有带有v-memo
指令,那就只需要加入renderList
形参对象即可。v-for
分析完毕。
分析v-memo
既然前面提到了v-memo
,那就接着去看v-memo
的语义分析。v-memo
的是靠transformMemo
,处理非常简单,验证指令存在且缓存中不存在当前节点,就会添加缓存,剩下的处理在退出函数中。
退出函数主要做的有两个工作,一是把非组件的子树变为block
,而后就是在codegen
外包一层函数withMemo
,v-memo
处理完成。
分析expression
表达式一共分为两种,一种是插值表达式:{{exp}}
,还有一种就是元素上的指令表达式,比如:v-bind:arg="arg"
,这些都交给transformExpression
。
首先是插值,它会去执行processExpression
,其实这个函数也没啥好看的,那么一大段代码,居然都是非浏览器环境下执行,在浏览器环境下执行只有一点一点。
进行简单的表达式验证,并且返回。
处理指令表达式时,不要处理v-on
和v-for
,它们会经过特殊处理,v-on
包装成内联语句。而v-for
会变成renderList
渲染。也是去执行processExpression
进行处理。如果arg存在并且不是静态的也需要处理。表达式处理完成。
结尾
本篇文章就先说v-for
、v-if
、v-memo
以及expression
的语义分析,其他的在后面再说。敬请期待。