插值表达式与事件绑定
Vue编译阶段将插值表达式 {{}} 转为 _s() 调用,事件绑定 @click 转为 on 配置对象。
插值表达式处理
文本节点解析
JavaScript
// 模板: <p>{{ msg }}</p>
// AST文本节点
{
type: 2,
text: '{{ msg }}',
expression: '_s(msg)'
}
// 解析过程
function parseText(text, delimiters) {
const tagRE = new RegExp(
escapeRegex(delimiters[0]) + '([\\s\\S]+?)' + escapeRegex(delimiters[1]),
'g'
)
if (!tagRE.test(text)) return null
const tokens = []
let lastIndex = tagRE.lastIndex = 0
let match, token
while ((match = tagRE.exec(text))) {
// 静态文本
if (match.index > lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex, match.index)))
}
// 插值表达式
token = `_s(${match[1].trim()})`
tokens.push(token)
lastIndex = match.index + match[0].length
}
// 剩余文本
if (lastIndex < text.length) {
tokens.push(JSON.stringify(text.slice(lastIndex)))
}
return tokens.join('+')
}
将 {{ msg }} hello {{ name }} 转为 _s(msg)+" hello "+_s(name)。
插值编译结果
JavaScript
// 模板
<p>{{ message }} - {{ count + 1 }}</p>
// 渲染函数
_c('p', [_v(_s(message) + " - " + _s(count + 1))])
插值通过 _s() 转为字符串,静态文本转为字符串字面量,用 + 连接。
事件绑定处理
v-on 解析
JavaScript
function processAttrs(el) {
const list = el.attrsList
for (let i = 0; i < list.length; i++) {
const { name, value } = list[i]
const dirMatch = name.match(dirRE)
if (dirMatch && name.match(onRE)) {
// v-on:click="handler"
name = name.replace(onRE, '') // 提取事件名
addHandler(el, name, value, modifiers, true)
}
}
}
提取 v-on 后的事件名,调用 addHandler 添加到 el.events。
addHandler 实现
JavaScript
function addHandler(el, name, value, modifiers) {
modifiers = modifiers || emptyModifiers
// 处理修饰符
if (modifiers.once) {
name = '!' + name // 标记once事件
}
if (modifiers.capture) {
name = '~' + name // 标记capture事件
}
if (modifiers.passive) {
name = '&' + name // 标记passive事件
}
// 存储到el.events
el.events = el.events || {}
el.events[name] = { value, modifiers }
}
通过前缀字符标记修饰符:! once、~ capture、& passive。
事件代码生成
JavaScript
function genHandlers(events, isNative) {
let res = isNative ? 'nativeOn:' : 'on:'
let str = ''
for (const name in events) {
str += `"${name}":${genHandler(name, events[name])},`
}
return res + `{${str}}`
}
function genHandler(name, handler) {
if (!handler) return 'function(){}'
if (Array.isArray(handler)) {
return `[${handler.map(h => genHandler(name, h)).join(',')}]`
}
const isPath = pathRE.test(handler.value)
const maybeMethod = fnExpRE.test(handler.value)
if (typeof handler.value === 'string' && !maybeMethod) {
return `function($event){${handler.value}}`
}
return handler.value
}
简单表达式直接返回函数引用,字符串表达式包装为 function($event){...}。
事件修饰符编译
JavaScript
// v-on:click.stop="onClick"
// 编译为
on: {
"click": function($event) {
$event.stopPropagation()
return onClick($event)
}
}
// v-on:submit.prevent="onSubmit"
// 编译为
on: {
"submit": function($event) {
$event.preventDefault()
return onSubmit($event)
}
}
修饰符在codegen阶段包装为事件对象方法调用。
综合示例
JavaScript
// 模板
<button @click="handleClick" :disabled="isDisabled">
{{ text }}
</button>
// AST
{
tag: 'button',
events: { click: { value: 'handleClick' } },
attrsMap: { ':disabled': 'isDisabled' },
children: [{ type: 2, text: '{{ text }}', expression: '_s(text)' }]
}
// 渲染函数
_c('button', {
on: { "click": handleClick },
attrs: { "disabled": isDisabled }
}, [_v(_s(text))])
事件、属性、插值分别编译到 on、attrs、子节点数组。
要点总结
- 插值表达式通过正则匹配提取,转为
_s(expr)调用 - 混合文本中插值与静态文本用
+连接 v-on解析事件名,通过前缀字符标记修饰符- 事件存储到
el.events,codegen生成on配置对象 - 字符串表达式包装为
function($event){...} - 修饰符如
.stop/.prevent编译为事件对象方法调用
📝 发现内容有误?点击此处直接编辑