以前就因为v-model踩过一次坑,没想到前几天再次踩坑。问题如下:
<input v-model="loginForm.phoneNumber" @input="limit" type="text">
limit(e) {
e.target.value = e.target.value.replace(/[^\d]/g,'')
console.log('value值-----'+e.target.value)
console.log('model值-----'+this.loginForm.phoneNumber)
}
使用了一个函数限制输入框只能输入数字,输入其他将会被置空,在输入1时是正确的,之后我们输入一个非数字,这个非数字并被置空之后,再输入新的数字,出现了下面这样的情况:
v-model的值和value的值不同,v-model无法获取到新输入的数值。
为什么会这样呢?百度半天无果,只好研究一波vue源码。v-model获取值是监听输入框的input事件,触发genDefaultModel 方法,如下便是vue中实现v-model绑定的方法,可以发现,在方法中有一句判断:code = `if($event.target.composing)return;${code}` ,这是什么意思呢?经查找MDN得知composing用来判断input事件是否由IME (即由文字输入法触发)构成触发的。
function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
): ?boolean {
const type = el.attrsMap.type
const { lazy, number, trim } = modifiers || {}
const needCompositionGuard = !lazy && type !== 'range'
const event = lazy
? 'change'
: type === 'range'
? RANGE_TOKEN
: 'input'
let valueExpression = '$event.target.value'
if (trim) {
valueExpression = `$event.target.value.trim()`
}
if (number) {
valueExpression = `_n(${valueExpression})`
}
let code = genAssignmentCode(value, valueExpression)
if (needCompositionGuard) {
code = `if($event.target.composing)return;${code}`
}
addProp(el, 'value', `(${value})`)
addHandler(el, event, code, null, true)
if (trim || number || type === 'number') {
addHandler(el, 'blur', '$forceUpdate()')
}
}
再看代码的其他部分,并没有发现什么还可能出现问题的地方,所以直接来测试一波是否当我们输入汉字后再输入数字时,composing的判断出现了问题,导致了v-model无法获取值。
limit(e) {
e.target.value = e.target.value.replace(/[^\d]/g,'')
console.log('是否由输入法触发-----'+e.target.composing);
console.log('value值-----'+e.target.value)
console.log('model值-----'+this.loginForm.phoneNumber)
}
果然,composing再继续输入数字时,每次判断仍然为true,导致了v-model无法获取到值。
为什么v-model会加一层composing判断?经过一番查阅之后,终于发现了原因。看如下代码:
<input :value="name" @input="format">
format ($event) {
this.name = $event.target.value.toUpperCase()
}
当我们输入中文时会发现,拼音全部转化成了大写字母出现在了输入框中,很明显,这是有问题的。所以v-model在实现时,加上了composing这一判断。
因为这个原因,所以elementui的inputnumber输入框也是使用change事件判断输入的内容。
但是为什么composing判断会出现问题?
composing 属性并不是标准 dom 元素属性,那它是怎么来的呢?
这里可以看出,composing 属性是 vue.js 添加到 dom 节点上的。
那么,是什么地方调用了这2个函数呢?可以看到,在插入dom节点时,vue.js 监听了 compositionstart / compositionend 事件:
compositionstart / compositionend 这2个 dom 事件,浏览器兼容性问题可以查阅 MDN 说明:
v-model 实际上是监听了 <input> 控件的 input、compositionstart、compositionend 三个事件,在输入法组合过程中就直接返回不赋值v-model 指令设置了变量 composing,此标识还用于判断是否更新 dom元素的 value 属性
v-model 输入中文触发的事件
从上面源代码分析可知,v-model 绑定 <input> 输入中文时,实际触发的事件如下:
compositionstart => 3个 input => compositionend 事件,这些都是 <input>控件触发的。最后一个 input 事件,是源代码里面看到的 onCompositionEnd 回调里面 vue.js 触发的。
1、compositionstart事件
修改dom对象的composing属性为 true
2、3个input事件
由于dom对象的composing属性为true,不会赋值,直接返回。
3、compositioinend事件
修改dom对象的composing属性为 false
4、vue.js触发的 input事件
由于dom对象的composing属性为false,赋值,修改相应变量的值。
回到我们开头所说的问题,应该是当输入汉字时,因为是非数字,所以被正则匹配替换为空,汉字未显示,从而导致composing未正确的被更改为false。