从vue.js的源码分析,input和textarea上的v-model指令到底做了什么
v-model
是 vue.js 中用于在表单表单元素上创建双向数据绑定,它的本质只是一个语法糖,在单向数据绑定的基础上,增加了监听用户输入事件并更新数据的功能;
对,它本质上只是一个语法糖,但到底是一个什么样的语法糖呢……?
正好最近我也踩到相关的坑了,就从最简单的input和textarea元素入手,分析一下v-model
这个指令到底做了什么吧
请先确认您已阅读过官方文档中关于v-model的部分
没有任何修饰符的 v-model
v-model 会根据绑定元素的类型,监听不同的输入事件,在 input 和 textarea 上,它默认监听的就是 input
事件;
简单点说,如果有这样一段模板:
<input v-model="name" type="text"/>
那么 v-model 的行为,就比较类似:
<input :value="name" @input="name = $event.target.value" type="text"/>
关于$event
变量,官方文档有介绍:
有时也需要在内联语句处理器中访问原生 DOM 事件。可以用特殊变量 $event 把它传入方法
虽然上面的例子描述了 v-model 最基础的行为,但二者的行为也不是完全一样,这次我踩到的坑也就在这里了……
官方文档中表单控件绑定章节有这么一条提示:
对于要求 IME (如中文、 日语、 韩语等) 的语言,你会发现
v-model
不会在 ime 构成中得到更新。如果你也想实现更新,请使用input
事件。
IME 的全称是 Input Method Editor 也就是我们常说的输入法
所谓 IME 构成,是指我们在输入文字时,处于未确认状态的文字,以微软的中文输入法举例:
图中带下划线的部分就属于 IME 构成,这些东西同样会触发input事件,但不会触发v-model更新数据……
本着踩进坑里了就不能白踩的精神,阅读了一下 vue.js 的源码:
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()')
}
}
可以看到,v-model
在编译的时候自动添加了一层判断,根据event.target.composing
判断此次input
事件是否是 IME 构成触发的……然而不知为什么,我没有在 MDN 上查到这个属性……
所以 v-model 的实际行为和这样的模板是一致的:
<input :value="name" @input="if($event.target.composing)return;name=$event.target.value" type="text"/>
当然也有例外,比如type="range"
的 input 元素就不会加这层判断,再怎么说滑块也是不会和 IME 构成有关的;
添加了 trim 修饰符的 v-model
trim修饰符的作用就是去除输入内容两侧的空格,这个没什么问题……v-model.trim
的行为类似于:
<input :value="name" @input="if($event.target.composing)return;name=$event.target.value.trim()" type="text"/>
添加了 lazy 修饰符的 v-model
lazy修饰符按照官方的描述是将默认监听的 input 事件改为监听 change 事件:
在默认情况下, v-model 在 input 事件中同步输入框的值与数据 (除了 上述 IME 部分),但你可以添加一个修饰符 lazy ,从而转变为在 change 事件中同步
而 change 事件一般是在输入元素失去焦点时才会触发,所以也不用考虑 IME 构成什么的了,v-model.lazy
的行为就类似于:
<input :value="name" @change="name=$event.target.value" type="text"/>
添加了 number 修饰符的 v-model
官方文档说这个修饰符可以自动把输入内容转为数字,然而具体是怎么转的数字并没有说……
如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符 number 给 v-model 来处理输入值
从之前的源码来看,vue.js使用了一个_n
函数来处理输入内容,也就是说v-model.number
的行为可以这样模仿:
<input type="text" :value="name" type="number" @input="if($event.target.composing)return;name =_n($event.target.value)">
……然而这个_n
是什么……
既然是能直接用的函数,那肯定能在 runtime 里找到,稍微翻了一下发现这个_n
实际上是一个toNumber
的工具函数,而toNumber
函数是下面这个样子的:
/**
* Convert a input value to a number for persistence.
* If the conversion fails, return original string.
*/
export function toNumber (val: string): number | string {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
可见,它是使用parseFloat
函数来转的数字,再使用isNaN
判断转换结果,如果结果是NaN,那么就返回原字符串,否则返回转为数字后的结果;
所以如果想完整模拟number
修饰符,只在内联语句中写就不太现实了……姑且尝试了一下,类似下面这个样子:
<input type="text" :value="name" type="number" @input="if($event.target.composing)return;name=isNaN(parseFloat($event.target.value))?$event.target.value:parseFloat($event.target.value)">
好吧,我承认这个修饰符很好用了……(:з)∠)
弄懂这个修饰符到底是什么之后,应该也就不会弄错了,不过反正都写到这里了,顺便就根据别人问过我的问题,整理一下number
修饰符几个常见的理解误区吧:
number 修饰符不能限制输入内容
这个修饰符的作用仅仅是把用户输入的内容尝试转换一下,如果转换成功了,那就是成功了,如果没成功,它也不会多管闲事的不让你输入进去;
就算输入的不是真实的数字,也可能会被转换
因为使用的是parseFloat
函数嘛……所以只要输入字符串的第一个字符是数字,最后就会被转成数字,比如这样
number 修饰符只会尝试转换,不会尝试计算
它只是一个简单的修饰符……请不要尝试让它做太多的事情……就算你输入一个1+1,更新数据时也不会变成2的……当然,也不会原封不动的把"1+1"这个字符串放进去,因为刚才说过了嘛……只要第一个字符是数字,parseFloat
这个函数就能把输入的字符串给你转成数字……←A←
以上就是今天要写的内容啦,如果再有什么新发现,我可能会再补充~
总的来说……vue.js 写的还是很精致的,也相当好用,源码也非常清晰,偶尔读读原理,想必也会对使用这个框架有或多或少的帮助吧
起码我觉得自己以后是不会再踩进 v-model 的坑了……
【下次抽时间也研究一下 select 和 checkbox 的 v-model 行为好了……
如果文章中有什么错误或者疏漏,请告诉我,我会尽快修改!
关于本文有任何问题也欢迎评论~
从vue.js的源码分析,input和textarea上的v-model指令到底做了什么的更多相关文章
- 死磕以太坊源码分析之MPT树-上
死磕以太坊源码分析之MPT树-上 前缀树Trie 前缀树(又称字典树),通常来说,一个前缀树是用来存储字符串的.前缀树的每一个节点代表一个字符串(前缀).每一个节点会有多个子节点,通往不同子节点的路径 ...
- 深入理解 path-to-regexp.js 及源码分析
阅读目录 一:path-to-regexp.js 源码分析如下: 二:pathToRegexp 的方法使用 回到顶部 一:path-to-regexp.js 源码分析如下: 我们在vue-router ...
- Java集合源码分析(七)HashMap<K, V>
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- Netty源码分析--内存模型(上)(十一)
前两节我们分别看了FastThreadLocal和ThreadLocal的源码分析,并且在第八节的时候讲到了处理一个客户端的接入请求,一个客户端是接入进来的,是怎么注册到多路复用器上的.那么这一节我们 ...
- 深入Vue.js从源码开始(二)
从入口开始 我们之前提到过 Vue.js 构建过程,在 web 应用下,我们来分析 Runtime + Compiler 构建出来的 Vue.js,它的入口是 src/platforms/web/en ...
- Vue.js 从源码理解v-for和v-if的优先级的高低
在vue.js里面,v-for和v-if是可以一起使用作用在某个元素上,网上看到一篇文章说永远不要把v-for和v-if同时用在同一个元素上,感觉有点瞎扯,官网也注明了可以一起使用的,还把两个指令的优 ...
- Disconf源码分析之启动过程分析上(1)
Disconf的启动,主要是包括两次扫描和XML非注解式配置,总共分为上下两篇,上篇先主要介绍第一次静态扫描过程. 先从入口分析,通过Disconf帮助文档,可以看到xml必须添加如下配置. < ...
- Netty源码分析--Channel注册(上)(五)
其实在将这一节之前,我们来分析一个东西,方便下面的工作好开展. 打开启动类,最开始的时候创建了一个NioEventLoopGroup 事件循环组,我们来跟一下这个. 这里bossGroup, 我传入了 ...
- vue源码分析—Vue.js 源码目录设计
Vue.js 的源码都在 src 目录下,其目录结构如下 src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不同平台的支持 ├── ser ...
随机推荐
- [洛谷P4656][CEOI2017]Palindromic Partitions
题目大意:一个长度为$n$的字符串,要求把它分成尽可能多的小块,使得这些块构成回文串 题解:贪心,从两边从找尽可能小的块使得左右的块相等,判断相等可以用$hash$ 卡点:无 C++ Code: #i ...
- BZOJ 1014 [JSOI2008]火星人prefix | Splay维护哈希值
题目: 题解: #include<cstdio> #include<algorithm> #include<cstring> typedef long long l ...
- POJ 2891 Strange Way to Express Integers | exGcd解同余方程组
题面就是让你解同余方程组(模数不互质) 题解: 先考虑一下两个方程 x=r1 mod(m1) x=r2 mod (m2) 去掉mod x=r1+m1y1 ......1 x=r2+m2y2 . ...
- POJ 3180 The cow Prom Tarjan基础题
题目用google翻译实在看不懂 其实题目意思如下 给一个有向图,求点个数大于1的强联通分量个数 #include<cstdio> #include<algorithm> #i ...
- 【CZY选讲·一道图论好题】
题目描述 LYK有一张无向图G={V,E},这张无向图有n个点m条边组成.并且这是一张带权图,不仅有边权还有点权.LYK给出了一个子图的定义,一张图G'={V',E'}被称作G的子图,当且仅当: · ...
- PHP error_reporting
E_ERROR 致命错误,脚本执行中断,就是脚本中有不可识别的东西出现 E_WARNING 部分代码出错,但不影响整体运行 E_PARSE 字符.变量或结束的地方写规范有误 ...
- highchart柱状图 series中data的数据构造
先可以看一下data的数据结构 网站http://jsfiddle.net/gh/get/jquery/1.9.1/highslide-software/highcharts.com/tree/mas ...
- MFC 在窗口上画指定大小的ICON
CPaintDC dc(this); HICON hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON) ...
- J2SE总结(一)-------容器
最近大家都在讨论容器以及如何在项目中去实际的应用它,由于之前对容器没有什么概念,所以把J2SE里面讲的容器的一些基础知识看了一下,总结一下最基本的东西. 围绕整章最核心的就属下面这张图了吧. 一.概念 ...
- openGL初学函数解释汇总
openGL初学函数解释汇总 1.GLUT工具包提供的函数 //GLUT工具包所提供的函数 glutInit(&argc, argv);//对GLUT进行初始化,这个函数必须在其它的GLUT使 ...