昨天我们分析了处理特殊标签以及inline style
,
坏蛋Dan:vue/compiler-dom源码分析学习--day2: 处理特殊标签&副作用标签
今天我们继续往下分析,该轮到指令了。
before
分析先来看下分析顺序
我们就按照这个顺序来分析。
我们的代码中没有这个指令,所以要补充下
由于代码只有一行,所以我把type
也放进来了。
可以看到指令最终会被转换成属性,而v-cloak
这个指令。。。有点尴尬,也不知道后面咋回事。。。
我们暂时mark
下先去分析别的了。
不过这里有一点我们知道了就是directive
会被处理成props
。
我发现我压根都没写任何的指令。。。
之前有个点貌似忘了说了,那就是这个exp
到底是个啥,看名字其实就知道了,是expression
表达式,比如这个v-html="html"
中的html
就是exp
。
至于loc
相信大家都清楚,就是定位代码位置用的,一般用于sourcemap
这个指令的代码很好理解
return
v-html
还想有子节点,有点贪心了,给它忽略掉子节点。innerHTML
的属性节点这个指令挺少用,因为大部分情况下都是mustache
也就是双大括号。
这个方法和v-html
差不多,都是不允许有子节点的存在。
这里有调用一个getConstantType
的方法,我们可以推测下这个方法是用来静态节点提升的,毕竟有constant
字眼。
并且传入的不是node
,而是exp
,这就意味着必定是一个表达式,所以我们就不看getConstantType
的具体代码了,反正到时候也会遇到,我们就看exp
对应类型的那一段
对应的ast
的type
是4
,而constantType
我们暂时不清楚是从哪来的,反正传入的node.exp
已经自带了
其实我们可以通过getConstantType
这个方法的返回值的类型中推测出来主要是用来干嘛的
我们的v-text
第一个例子传入的是一个变量,这里拿到的constantType
是0
,而传入字符串的则是3
。
一个是NOT_CONSTANT
,一个则是CAN_STRINGIFY
。
这里有句注释:Higher levels implies lower levels.
啥意思呢?应该是这个枚举中对应的数字越高越“安静”越稳定不变。 都能直接stringify
了,那就说明完全不可变了,相反not constant
则表示可变。
如果是not constant
的话,会变成下面这样
createCallExpression
: 看名字就知道是创建一个call expression
,callee
则是调用的函数名字所以说这个表达式最终应该是((_ctx) => _toDisplayString(_ctx.text))
。
这块相信大家都比较熟悉了
但是在看代码之前,得先知道一种用法,给子组件绑定v-model
的场景[5]
相当于props
+ emit
你可以在这里试一下,有一点需要注意的是vue3.x
里子组件接收的对应的prop
得是modelValue
这个名字(默认,可自行定义),不然就无法双向。
代码有点长
transformModel as baseTransform,
: 也就是说@vue/compiler-core
包里其实有转换model
的方法,但是这里又重写了。这个baseTransform
咱们先不看,先看这个transformModel
里做了什么
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT
: 这个报错看下对应的文本(突然想起前面几个都没有看错误提示文案。。。不过没差,都认识是啥报错)也就是你这个v-model
指令不能含有argument
,那是啥情况呢?实际上是这个情况
不需要我们再去定这个value
,因为这个是默认的。
V_MODEL_TEXT
: 表示当前v-model
绑定的类型type
为什么要确定类型呢?因为v-model
只是看起来很方便,实际上没那么神奇,只是判断类型做不同转换,脏活累活都交给编译器了。
V_MODEL_DYNAMIC
:表示这个type
是动态的,比如:type="type"
,现在还不能确定是啥,只能等runtime
了。噢,前面有个点忘了说了,你使用的:xxx
,这个:
大家应该都记得是语法糖吧,所以在编译的时候等价于v-on:xxx
X_V_MODEL_ON_FILE_INPUT_ELEMENT
: 来看下错误提示文案:v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.
这玩意儿不支持file
类型,用@change
替换。checkDuplicatedValue
: 除了radio
、checkbox
之外,能用v-model
的是类似text
的类型,比如number
,而这个时候,并不需要我们去给你的节点绑定一个value
属性,因为v-model
默认找的就是value
,而radio
和checkbox
需要多个节点配合,所以需要确定value
。hasDynamicKeyVBind
: 这个方法代码就不看了,简单的说就是判断你这个节点动态绑定的属性里有没有type
这个属性,比如这种v-bind="{ type: 'checkbox' }"
,不过这段得交由runtime
去处理了,给它定为dynamic
context.helper(directiveToUse)
: 看到helper
就大概率是runtime
相关的,这块实际上是注入runtime
需要的方法,通过directiveToUse
这个key
确定下来的类型来判断注入哪个runtime
方法(helper
)。从这里你应该也能想到一些render function
如何生成的逻辑,比如这块import helper
,其它情况也都差不多。
最后做了一层filter
操作,把value="modelValue"
这个props
给去掉了,为什么呢?因为它们(非组件)不需要,这个modelValue
是vue3.x
提供给组件使用v-model
的,所以其它都不需要,而需要的组件早就被return
掉了。
组件不需要runtime
的helper
,因为组件使用v-model
是props + emit
的语法糖。
而baseTransform
我放到其它
这一章节里。
有一点需要注意,那就是上面的代码不会涉及到components
。
v-on
但是是@vue/compiler-core
里的分析了一大半突然发现分析错包了。。。。。。。。。。。。。。。。。。。。。。。。 算了,这个就将错就错了,我说量怎么这么大呢。。。不过好在@vue/compiler-dom
里的这个vOn
是基于这个core
包里的vOn
的,也得分析。
这个相信大家都耳熟能详了,不过大部分情况下我们都是使用语法糖写法@xxx
。
在开始分析代码之前,我们需要知道有几种用法,这样才能方便我们分析
把这些一股脑全给贴到我们的team.vue
里。
ok
,现在可以来看代码了
代码也是有些长
dir
:directive
,不多说。arg
: v-on
绑定的变量。exp
: 表达式。modifier
: 修饰符。X_V_ON_NO_EXPRESSION
: 这个应该不用多说,直接看下错误文案即可。v-on is missing expression.
不过有一点需要注意,那就是有的场景并不需要表达式,有个修饰符就可以了,比如@submit.prevent
就不需要传入表达式。isStatic
: 也是老朋友字段了,如果你是这样的: @[event]
,那就是false
。vue:
这个是个啥玩意儿呢?官方文档(其实我一直在api
文档里找。。。)里我找了半天都没找到,最后跟着git
记录搜索了下,发现了这个迁移文档原来是用来监听自组件的生命周期的,vue2
中是hook:
,3.x
改成vue:
并且能监听html
元素,太得劲了
camelize
: 看名字就是帮你把-xxx
转换成大写X
toHandlerKey
: 有点套,结合上面的camelize
整理一下。toHandlerKey(camelize(rawName))
实际上是先将你的横杠小写的规范改成词首字母大写的规范,比如
it-is
变成itIs
。然后调用toHandlerKey
先把你的首字母大写变成ItIs
,然后再on
包裹,变成onItIs
。
而cacheStringFunction
是一个高阶函数,里面用到了闭包。接收一个function
,返回一个function
。
这里有两个东西缓存下来了,一个是cache
,一个是fn
,每次往里面存放str
的时候如果没有在cache
里找到对应的值,就调用fn
生成并缓存。
那么总结下toHandlerKey
这个方法是干啥用的:
on
所以这一段是在整理rawName
也就是监听的事件名字
注意这里的条件
组件/上面说到的使用hook:
(2.x
)或者vue:
(3.x
,这也就是为什么vue3.x
生命周期函数名字改了但是这里使用不需要同步改动的原因)/没有大写的事件名(这就是内置元素的事件名,规范就是全小写字母并且不能用下划线)。
注意这里的组件,我们来看下这一行代码之后的代码
我把注释也贴进来了,这里有说到custom elements
,翻译过来也就是自定义元素。
这是个啥?不是组件吗?是的,不是。vue
允许你去定义一些自定义元素,比如一些浏览器支持的标签但是没有被vue
收录的。
Vue and Web Componentsvuejs.org/guide/extras/web-components.html#using-custom-elements-in-vue
vue
的编译器在遇到不认识的tag
也就是标签的时候默认把它当作是一个组件,而对于自定义元素来说就会引起报错:failed to resolve component
。所以vue
官方提供了选项,允许过滤掉(不处理,保留)自定义元素。
先来看下vue2
里的做法
通过Vue.config.ignoreElements
来忽略(保留不改动)它们。 但注意,这是在runtime
。
vue3.x
迁移到了编译阶段(非in-browser
),来看下vite
的配置
或者vue-cli
ok
,回到我们的代码中,我们看到自定义元素都不处理,并且加上on:
的前缀。
TO_HANDLER_KEY
: 实际上就是toHandlerKey
这个方法的runtime
同名方法
为啥这么绕口呢。。。因为这里无法处理dynamic
也就是isStatic
为false
的场景,即@[event]="xxx"
这种写法,由于无法确定具体的eventName
,所以只能是交由runtime
去解析处理。
createCompoundExpression
: 这个方法前面见过好几次了,每次都出现在dynamic
的场景。对应节点的type
为NodeTypes.COMPOUND_EXPRESSION
也就是8
。v-on
绑定的事件名(也就是arg.content
)不是NodeTypes.SIMPLE_EXPRESSION
也就是type == 4
呢?来看下下面这种写法这个type == 8
自然就是上面NodeTypes.COMPOUND_EXPRESSION
,需要runtime
的时候才能处理。
上面说完了arg
的处理,接下来轮到exp
了。
context.inVOnce
: 这个应该是.once
的操作。MemberExpression
[8]: 你应该注意到了,前面好几处出现了,但是都没有说。这玩意儿真的有些难懂,翻译过来叫做成员表达式。。。。这谁懂,然后babel
对应的那块逻辑压根没给出例子。没得办法只能上网找大佬们的解释。**memberExpression**
这里推荐一下这个网站,每一个词都会有对应的节点展示
这个obj.a()
中的obj.a
就是一个memberExpression
我们再来看个例子,我把obj.a
改成obj.a.b
可以看到,这里出现了两个memberExpression
,一个是obj.a
,一个是obj.a.b
。也就是说这玩意儿是支持套娃的。
然后我们再来看下一个例子
一个数组调用元素就是一个memberExpression
,这点又和xx.xx
这样的逻辑不同。需要注意,用[]
来调用的场景computed
会是true
,反过来也就是说computed
为true
,那就是通过[]
去读取的方式。
说了这么多,该归纳下了
xx.xx
这种和xx[xx]
这种又或者xx[xx].xx
都是memberExpression
。
ok
,回到代码中,这里有点想吐槽知乎的章节结构最多两层。。。好歹支持个三层吧。。
isMemberExpression
这个方法里做了什么判断就不看了,有点伤脑力,直接看这个名字就知道针对memberExpression
这种节点的。isInlineStatement
:顾名思义,inlineStatement
就是类似SIMPLE_EXPRESSION
的样子。fnExpRE
: 来看下正则看不大懂没事,关键字认清楚即可async function
,() => {}
。一个行内function
,这个也要归纳到非inlineStatement
的场景。
hasMultipleStatements
: 很好理解,多条语句,比如下面这种这样是可行的。另外这也算是inlineStatement
。
processExpression
: 看名字是在说加工表达式,代码我就放到其它
里了,量有些大 。简单的说就是在处理表达式,最终生成一个新的节点。removeIdentifiers
: 然后又移除呢?因为你这个表达式可能是一个函数调用表达式然后你的参数里有个$event
,比如@click="a($event)"
,这个时候就需要补上$event
作为默认参数。至于为啥要特意在这个processExpression
的阶段补上这个$event
到context.identifier
呢?因为这个表达式会被拆分为a
和$event
,这个$event
如果分析的时候不认识就会被当作_ctx.$event
,所以这里就需要给加上。至于之后又移除,这自然是避免污染上下文变量集合context.identifier
。shouldCache
一看就是用来判断是否可以缓存的标志位的,不过有两种判断条件。如果没有exp
也就是没有表达式,那么只需要存在cacheHandlers
和没有使用.once
修饰符。毕竟.once
只执行一次没必要缓存。而如果有表达式,那么缓存条件就有些复杂了。有cacheHandlers
和非.once
这两个是必然的,我们来看下在有表达式的场景下还有哪些条件!(exp.type === NodeTypes.SIMPLE_EXPRESSION && exp.constType > 0)
这个条件就有点怪了,看下注释runtime constants don't need to be cached.(this is analyzed by compileScript in SFC <script setup>)
。也就是说runtime
的const
变量不需要缓存处理,它们由compileScript
控制。怪了,我们之前虽然没有分析,但是cache
实际上只有在import
的阶段有,难不成是bindingMetadata
.先mark
吧。!(isMemberExp && node.tagType === ElementTypes.COMPONENT)
如果是组件,并且表达式是一个比如a.b
或者a['b']
这种。这样也不缓存,为什么呢?因为它的源头可能不见了,比如a.b
里的a
挂了,这个时候如果还在缓存阶段,就有问题了。所以需要整个引用源都保护起来。!hasScopeRef(exp, context.identifiers)
这个比较好理解,不能引用v-for/v-slot
里的变量,为什么呢?因为这样产生闭包,到时候容易存在内存泄漏。augmentor
: 这个就是@vue/compiler-dom
这个包里的vOn
传入的回调,我们等会再回去看下。总结一下
eventName
的节点,具体请往回看。exp
的节点,具体请往回看。($event/...args) => { exp }
objectProperty
的节点,key
就是第一点里的节点,value则是第二点表达式的节点。@vue/compiler-dom
里的vOn.ts
传入的回调(待分析)value
做一层缓存处理,这样数据就不会发生变化,这样能避免组件的重复渲染。key
为isHandlerKey
,表示这是一个handler
,到时候需要执行。props
来看下最终数据
而我的原始代码是
v-on
上面终于分析完了@vue/compiler-core
包里的v-on
,也就是@vue/compiler-dom
的vOn.ts
里的baseTransform
,但是还留了一个argumetor
回调没有分析,而这个回调就在@vue/compiler-dom
的vOn.ts
中。
dir
: 就是directive
,就是我们的@click=“xxx”
node
:自然就是这个dir
所在节点,比如<button>...</button>
modifiers
: 前面说过了,比如@click.stop="xxx"
,这个stop
即是modifier
,也就是修饰符baseResult
,也就是我们刚分析完的baseTransform
最后返回的compund
节点。resolveModifiers
: 看名字是用来处理修饰符的,我们来看下代码resolveModifier
先来看下v-on
的修饰符
checkCompatEnabled
: 判断是否是可兼容该修饰符的版本,native
修饰符是准备废弃掉了[9][10]。代码就没必要看了。maybeKeyModifier
: left,right
这俩货checkCompatEnabled
: 判断是否是可兼容该修饰符的版本。native
修饰符是准备废弃掉了isEventOptionModifier
: passive,once,capture
这仨货maybeKeyModifier
: left,right
这俩货isKeyboardEvent
: onkeyup,onkeydown,onkeypress
这三个事件isNonKeyModifier
: stop,prevent,self,ctrl,shift,alt,meta,exact,middle
这些剩下的这个方法简单的说就是在给modifier
也就是修饰符分门别类。
然后回到我们的transformOn
中
transformClick
: 来看下代码这个方法挺好理解的,如果是写死的click
,比如@click.middle/right
,那么直接就替换为mouseup/onContextmenu
,如果是动态的@[event].left/right
,那就得判断,如果命中click
事件就替换。
isKeyboardEvent
: 是否是键盘点击事件onkeyup,onkeydown,onkeypress
这三货总结下@vue/compiler-dom
里的vOn.ts
做了什么
其实做的事情很简单,处理modifier
,表达式和事件名已经被@vue/compiler-core
里的vOn.ts
处理完了。
先是判断native
这个修饰符的兼容性,如果可用就依旧保留,放在eventOptionModifier
也就是事件选项修饰符队列中。
然后整理整理下v-on
修饰符的四种类型
passive,once,capture,native(需要考虑兼容性)
left,right
,可以被鼠标/键盘使用(需要根据具体事件类型:onkeyup,onkeydown,onkeypress
)stop,prevent,self,ctrl,shift,alt,meta,exact,middle
.{keyAlias}
最后一种算在键入事件修饰符队列中,也就是keyModifiers
。
接着重写使用.right
和.middle
修饰符的事件,如果是事件名是静态的并且是click
事件,直接将事件名替换为onContextmenu/mouseup
,如果是静态非click
,保持原来的事件名,而如果是动态的事件名,这个时候就得创建compoundExpression
节点,runtime
的时候再去判断事件类型,如果到时判断是click
,那就替换,否则不做处理。
然后封装事件名对应的value
,也就是表达式节点,比如@click="xxx"
的xxx
被core
包的vOn
转换后的节点。
vOnModifiersGuard
这个runtime
的辅助函数包裹起来onkeyup,onkeydown,onkeypress
这仨货,如果是,则将表达式节点和修饰符用vOnKeysGuard
这个runtime helper
包裹起来。最后重新拼接为props
。
v-show
V_SHOW
:也是有点没有没脑的,先mark
起来。
v-model
的baseTransform
代码有点小长
prefixIdentifiers
: 暂时还不知道是干啥用的。isSimpleIdentifier
: 一个正则,什么意思呢?如果这个v-model
传递的变量如果没有.
这玩意儿,就意味着这变量不是来自ctx
的,那么就是template local
变量,这种是不允许的,看下面的一点。 或者是一个函数调用表达式,比如a()
,也是不允许的。X_V_MODEL_ON_SCOPE_VARIABLE
: 来看下错误文案:v-model cannot be used on v-for or v-slot scope variables because they are not writable.
也就是v-model
传入的表达式不允许是v-for
或者v-slot
里的变量,因为它们不可写(not writable
)。也就是上面说的template local
变量。rawExp
: 原始的表达式,存储的就是你代码写的,比如v-model="xx"
,这个xx
就是原始的表达式,而转换后会变成_ctx.xx
也就是下面的exp.content
。exp.content
:也就是上面说的_ctx.xx
。bindingMetadata
: 我记得在说compileScript
的时候有说过,用来绑定数据的类型,但是这是编译状态,怎么会有来自script
的bindingMetadata
呢?其实还有inline template
也就是inline mode
,只不过我们并没有分析isStaticExp
:propName
: 也就是传入子组件或者那几个标签的props
属性。eventName
:自然就是onUpdate:xxx
。你应该注意到了这个arg
了,这个arg
自然和上面提到过的那个arg
是同一个,而这里居然有static
之分?实际上这个是vue3.x
提供的语法[12]
两种写法都是可以的,区别在于bb
会去找对应的数据再转换为字符串也就是dynamic
,而dynamic
自然就只能是runtime
的时候才能确定了。
注意,这仅能用于自定义组件使用,其它比如input
、textarea
都是不能用的。
来看下数据
当然,如果没有,默认使用modelValue
这个名字。
maybeRef
: 看名字就知道是判断是否是ref
变量,之前我们分析script
的时候有说过如何判断这个变量的类型的,当然,并不一定准确,有些变量可能在runtime
的时候变成了ref
或者unref
。你应该看到了else
语句也有一个表达式生成,这也就是说你定义在setup
里的unref
变量也是可以通过v-model
进入到自组件的。毕竟变量定义在setup block
中最终是会被return
出来的,不管是不是ref
,我们在分析script
的时候说过了,这里就不多说了。
createObjectProperty
:生成一个type == JS_PROPERTY
的属性节点
hasScopeRef
: 代码我们就不看了,简单的说就是判断这个节点以及它里面的所有节点是否有引用/使用到template local
变量(仅当前环境,该方法较通用,具体参数具体分析)。context.cache(props[1].value)
: 给数据节点做一层cache
处理,这里暂时不知道怎么处理的,所以先mark
下来。modifiers
[13]: 看名字就知道是修饰符,比如.lazy
之类的,在组件中是以props
的方式传入一个对象默认名字是modelModifier
,实际会根据你的arg
名字来生成,规则是${arg.content}Modifiers
,这个prop
默认是一个空对象。简单的总结下这个方法
template local variable
也就是非_ctx.xxx
,而是v-for
等场景自带的variable
。arg
也就是绑定的变量名,允许动态传入(动态的无法确定,所以只能是runtime
的时候再去处理)${arg.content}Modifiers
为名的prop
传入,默认是一个空对象。{ props }
,这个是props
默认存在三个字段modelValue
、update:moduleValue
以及modelModifier
漏了一个点,那就是这个update:modelValue
传入的value
是一个回调,而 $emit('update:modelvalue')
实际上只是去执行这个回调。
processExpression
代码有些长
rawExp
: 原来的表达式isSimpleIdentifier
: 前面讲过,判断你是否是template local variable
isGloballyWhitelisted
: 直接看下面这图isLiteralWhitelisted
: 字面量白名单。BindingTypes.SETUP_CONST
: 这个前面讲过挺多次的,首先他得是在setup block
或者选项式里的setup
属性里,然后得是非ref/reactive
的const
变量。不过在编译过后它也是会被setup
抛出的,这点之前分析compileScript
就说过了,所以自然也能用在template
里。ConstantTypes
: 是一个枚举,好像前面讲过来着。。等级越高越“静”。CAN_SKIP_PATCH
:意思是打补丁(diff
新旧vnode
发生在这个时候)的时候应该不用管他了,可以绕过。rewriteIdentifier
: 来看下这个方法hasOwn
: 这个没啥好看的,就是判断这个对象是否包含这个属性bindingMetadata
:还记得之前说的吗?我们并没有分析inlineTemplate
的场景,而inlineTemplate
场景是在compileScript
的时候就进行的。而这个bindingMetadata
就是之前compileScript
调用@vue/compiler-dom
的compile
时传入的。它是一个集合,包含着setup
这一块里面所有变量以及他们的类型,key
是变量名,而value
则是这个变量在setup
中是什么类型,比如setup-ref
或者setup-may-ref
等。isAssignmentLVal
: 赋值表达式左边,比如x = y
的x
isUpdateArg
: 自加表达式, 比如x++
isDestructureAssignment
: 解构表达式,比如({ x } = y)BindingTypes
: 之前说过,它是一个枚举,里面存放的是所有setup block
可能出现的变量类型。UNREF
: 前面说过了,unref
判断你是否是ref
变量,是的话返回值,不是则返回这个变量。注意这是个runtime
函数
IS_REF
: 这个就更不多说了genPropsAccessExp
: 看下代码就是获取你这个defineProps
变量的属性。
BindingTypes.PROPS_ALIASED
: 还记得我们分析过的defineProps
解构赋值的场景吗?比如const { a: _a } = defineProps({...});
。__propsAliases
: 上面这个_a
可能会被用在template
中,所以这里需要获取对应的原变量名。总结一下这个rewriteIdentifier
方法做了什么
一、inlineTemplate
场景,场景就比较多和复杂
setup
块里的非ref
的const
变量/使用reactive
包裹之后的变量,这些都不用处理的,该怎么样就怎么样。setup ref
的变量,那就改下返回的数据为.value
,毕竟ref
数据都在.value
里。SETUP_MAYBE_REF
,也就是这个变量在编译的时候并不能知道它是个啥,场景其实挺多的,比如:const { a } = defineProps({...}); const b = a;
。这个时候编译器拿到a
压根就不知道是啥, 而a
是通过props
传入的一个ref
对象。但是编译阶段是做不到跨组件的,说到底就是一个个sfc
文件的编译转换。所以这个时候如果你的表达式是一个会改变变量对应的值或者被解构,如果不给它加上.value
就会有问题,所以以下这三个场景需要特殊处理:1. 赋值表达式; 2. 自加/自减; 3. 解构场景。这个将值变成x.value
,这样才不会有问题。当然这里你应该会有些奇怪,为什么这三个场景不用判断是否是ref
就直接返回.value
了。因为你这如果是一个普通变量那么这些表达式改变了值又有什么用呢,并不会应用于变量本身。然后除此之外的其它场景就直接判断是否是ref
再返回值/变量了。BindingTypes.SETUP_LET
: 这个用尤大大的原话来说就是:tricky
,太狡猾了,为什么呢?因为它们可以在runtime
的某一时刻变成ref
又或者non-ref
。编译器针对上面的三个场景分别做了以下处理:ref
,是得话帮它加上.value
,但是等号右边可能又是一个ref
,所以这个时候就得递归把右边也放进去拿到对应的值才行。最后的表达式会变成x = y --> isRef(x) ? x.value = y(.value) : x = y(.value)
。ref
然后加上.value
即可。不过由于自加自减有两种情况,++a
或者a++
,所以这个时候要先判断是++a
还是a++
才行。最后的表达式会变成a++ -> isRef(a) ? ++a.value(++) : ++a(++)
。very stricky
。目前没处理,仅是把它当作non ref
,然后返回当前的变量。unref
处理。BindingTypes.PROPS
: genPropsAccessExp
上面说过了,返回_props.xxx
或者_props[xxx]
的表达式。BindingTypes.PROPS_ALIASED
: 同上,不过得先找到对应的真名。二、 传统non-inline
setup
变量,比如const a = 1
或者let b = 1
,都会直接从$setup
这个上下文中获取props
通过解构的方式给的别名,这个时候就得从$props
上下文中获取对应的真名。三、最后返回_ctx.${raw}
,兜底。
看到这里你应该知道这个方法仅能用于简单的表达式。
ok
这玩意儿终于讲完了,我们接着回到我们的processExpression
中
parse
: 自然就是@babel/parser
[14]里的parse
。asRawStatements
: 这个是之前传入的变量const hasMultipleStatements = exp.content.includes(';')
, 用;
分隔判断是否是多个表达式。比如a++; b();
前面有说到过了,这样是可以的。walkIdentifiers
: 这个就不看代码了,前面分析compileScript
的时候有说到过,简单的说下,它也是基于estree-walker
[15]这个包来改变节点的位置,这个包之前有提到过。这里用它来遍历所有节点。__COMPAT__
: 全局变量,用来判断兼容V2
里的一些语法的,比如filter
,在3.x
就废弃了。isReferenced
: 应该是指的这个expression
是否是一个引用的,这里就不纠结来源了,里面涉及到的代码很多,后面如果遇到再分析了。isLocal
: 是否是template local
变量canPrefix
:暂时不清楚这个canPrefix
是做什么的,不过看名字可以推测是要加上前缀,而一些全局白名单对象以及 webpack
特有的require
引入都是false
。
isStaticProperty
: 前面说过好几次了,这里就不多说了.shorthand
: 指的是简写,比如{ a: a }
可以缩写成{ a }
.QualifiedId
: 这个给忘了,来看下估计你应该忘了我们的node
长啥样了 ,这里再看一次,因为后面需要和这个方法作用域里的node
区分开来
ids
:多表达式转换成数组leadingText
: 两个表达式变量之间的内容,比如children
: 说那么多不如一张图直接createCompoundExpression
: 这个之前说过很多次了,如果是编译过程中无法处理的问题,那就是动态dynamic
的,这个时候就会创建一个compoundExpression
交给runtime
处理了。ret
: 最后返回的数据ok
,让我们来总结下这个方法做了什么
根据当前表达式是否带有;
分为下面两种情况
一、单表达式
template local variable
&& 不是内置全局变量&&非字面量,这种时候就会调用rewriteIdentifier
匹配重写这个表达式的节点。另外,如果是setup-const
类型的节点,会被标记为CAN_SKIP_PATCH
,也就是打补丁阶段可以直接跳过,毕竟是一个固定的变量。template local variable
也就是v-for
或者v-slot
产生的变量。这种场景下,判断是否是字面量,如果是则可以直接CAN_STRINGIFY
,也就是以后都可以直接不理会。如果不是字面量,那就有可能是全局的变量,比如JSON
等,这种被标记为CAN_HOIST
,也就是提升。这个CAN_STRINGIFY
是最稳定的,CAN_HOIST
次之。单表达式又存在两种场景
inline
non-inline
至于如何分辨、处理,请往回看rewriteIdentifier
的总结。
标记最终的节点constType
类型,如果是带有.
或者()
的单表达式,那么标记为NOT_CONSTANT
,否则是CAN_STRINGIFY
。也就是说,要么是可变的,要么是不可变的。
返回最终的节点。
二、多表达式
@babel/parser
的parser
方法将表达式转换成ast
estree-walker
遍历ast
,收集所有表达式里的变量也就是indentifier
,如果是常规变量则会调用rewriteIdetifier
处理,否则除了v-for/v-slot
中产生的变量之外(比如字面量),其它都标记为isConstant
。loc.start/end
)排序收集到的变量,然后顺便截取这些个变量之间的内容组成一个children
的数组children
转换为一个compoundExpression
节点,然后返回。简单的说就是根据不同场景处理表达式,转换成一个节点。
今天我们分析了几个指令是如何处理的,其中v-model
以及v-on
的比较麻烦,毕竟场景较多。而v-cloak
/v--show
有点摸不着头脑,需要mark
下来,后面应该还会遇到。
如果觉得这篇文章对你有帮助,请务必点个赞,谢谢~
发布于 2022-12-21 11:21・IP 属地广东