我们先打开文件src\compiler\parser
。
export default function model (
el: ASTElement,
dir: ASTDirective,
_warn: Function
): ?boolean {
warn = _warn
const value = dir.value // v-model绑定的值
const modifiers = dir.modifiers // 指令修饰符
const tag = el.tag // 指令节点名称
const type = el.attrsMap.type // input标签的type类型
if (process.env.NODE_ENV !== 'production') {
// input标签的类型不能是 type="file" 因为file的值是只读的,
if (tag === 'input' && type === 'file') {
warn(
`<${el.tag} v-model="${value}" type="file">:\n` +
`File inputs are read only. Use a v-on:change listener instead.`,
el.rawAttrsMap['v-model']
)
}
}
if (el.component) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers)
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value, modifiers)
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el, value, modifiers)
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime
return false
} else if (process.env.NODE_ENV !== 'production') {
// 标签不支持v-model指令
warn(
`<${el.tag} v-model="${value}">: ` +
`v-model is not supported on this element type. ` +
'If you are working with contenteditable, it\'s recommended to ' +
'wrap a library dedicated for that purpose inside a custom component.',
el.rawAttrsMap['v-model']
)
}
// ensure runtime directive metadata
return true
}
下面我们对这个代码进行一分析:
if (process.env.NODE_ENV !== 'production') {
if (tag === 'input' && type === 'file') {
warn(
`<${el.tag} v-model="${value}" type="file">:\n` +
`File inputs are read only. Use a v-on:change listener instead.`,
el.rawAttrsMap['v-model']
)
}
}
首先看到是input标签的类型进行了一个判断,不能是 type="file" 因为file的值是只读的,不能用代码进行赋值,只能赋值为空字符串来清空值。
紧接着是对各种input类型进行处理,我们本次的例子的type="text"默认的,所有我们主要是来分析genDefaultModel(el, value, modifiers)
函数的处理。
我们来看看genDefaultModel
的具体代码:
function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
): ?boolean {
const type = el.attrsMap.type // input标签的type类型
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
if (process.env.NODE_ENV !== 'production') {
const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
if (value && !typeBinding) {
const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
warn(
`${binding}="${value}" conflicts with v-model on the same element ` +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
)
}
}
// 这个就是v-model 指令的3个修饰符
// lazy 监听change事件
// number 将输入转换为数字
// trim 过滤前后空格
const { lazy, number, trim } = modifiers || {}
const needCompositionGuard = !lazy && type !== 'range'
const event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input'
// valueExpression 计算value的表达式
let valueExpression = '$event.target.value'
if (trim) {
valueExpression = `$event.target.value.trim()`
}
if (number) {
// _n 就是一个toNumber函数,内部调用'parseFloat'进行强制类型转换
valueExpression = `_n(${valueExpression})`
}
// 用于生成v-model指令的赋值代码
let code = genAssignmentCode(value, valueExpression)
if (needCompositionGuard) {
// 输入的延时更新 https://zhuanlan.zhihu.com/p/55396545
code = `if($event.target.composing)return;${code}`
}
// 给Prop数组添加值
addProp(el, 'value', `(${value})`)
// 添加事件处理
addHandler(el, event, code, null, true)
// 存在trim 和 number 修饰符的话,就强制更新
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()')
}
}
我们来看看具体的代码分析:
if (process.env.NODE_ENV !== 'production') {
const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
if (value && !typeBinding) {
const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
warn(
`${binding}="${value}" conflicts with v-model on the same element ` +
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
)
}
}
可以看到如果存在了v-model指令,就不要去绑定value值了,如果需要绑定value的值,那就需要绑定type类型。
最后会在生成DOM节点的时候调用addEventListener添加事件绑定。