v-model数据绑定分析
v-model数据绑定分析
v-model是Vue提供的指令,其主要作用是可以实现在表单<input>、<textarea>及<select>等元素以及组件上创建双向数据绑定,其本质上就是一种语法糖,既可以直接定义在原生表单元素,也可以支持自定义组件。在组件的实现中,可以配置子组件接收的prop名称,以及派发的事件名称实现组件内的v-model双向绑定。
描述
可以用v-model指令在表单<input>、<textarea>及<select>元素上创建双向数据绑定,其会根据控件类型自动选取正确的方法来更新元素,以<input>作为示例使用v-model。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
msg: ""
},
template: `
<div>
<div>Message is: {{ msg }}</div>
<input v-model="msg">
</div>
`
})
</script>
</html>
当不使用v-model语法糖时,可以自行实现一个双向绑定,实际上v-model在内部为不同的输入元素使用不同的property并抛出不同的事件:
input和textarea元素使用value property和input事件。checkbox和radio元素使用checked property和change事件。select元素将value作为prop并将change作为事件。
同样以<input>作为示例而不使用v-model实现双向绑定。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
msg: ""
},
template: `
<div>
<div>Message is: {{ msg }}</div>
<input :value="msg" @input="msg = $event.target.value">
</div>
`
})
</script>
</html>
对于v-model还有修饰符用以控制用户输入:
.trim: 输入首尾空格过滤。.lazy: 取代input事件而监听change事件。.number: 输入字符串转为有效的数字,如果这个值无法被parseFloat()解析,则会返回原始的值。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: "#app",
data: {
msg: 0
},
template: `
<div>
<div>Message is: {{ msg }}</div>
<div>Type is: {{ typeof(msg) }}</div>
<input v-model.number="msg" type="number">
</div>
`
})
</script>
</html>
当使用自定义组件时,在组件上的v-model默认会利用名为value的prop和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value attribute用于不同的目的,此时可以使用model选项可以用来避免这样的冲突。
<!DOCTYPE html>
<html>
<head>
<title>Vue</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
Vue.component("u-input", {
model: {
prop: "message",
event: "input"
},
props: {
message: {
type: String
},
},
template: `
<div>
<input :value="message" @input="$emit('input', $event.target.value)">
</div>
`
})
var vm = new Vue({
el: "#app",
data: {
msg: ""
},
template: `
<div>
<div>Message is: {{ msg }}</div>
<u-input v-model="msg"></u-input>
</div>
`
})
</script>
</html>
分析
Vue源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit id为ef56410。
v-model属于Vue的指令,所以从编译阶段开始分析,在解析到指令之前,Vue的解析阶段大致流程:解析模版字符串生成AST、优化语法树AST、生成render字符串。
// dev/src/compiler/index.js line 11
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options) // 生成AST
if (options.optimize !== false) {
optimize(ast, options) // 优化AST
}
const code = generate(ast, options) // 生成代码 即render字符串
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
对指令的处理就在生成render字符串的过程,也就是generate函数的处理过程,在generate中调用genElement -> genData -> genDirectives,文章主要从genDirectives函数进行分析。
// dev/src/compiler/codegen/index.js line 43
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`, // render字符串
staticRenderFns: state.staticRenderFns
}
}
// dev/src/compiler/codegen/index.js line 55
export function genElement (el: ASTElement, state: CodegenState): string {
// ...
data = genData(el, state)
// ...
}
// dev/src/compiler/codegen/index.js line 219
export function genData (el: ASTElement, state: CodegenState): string {
// ...
const dirs = genDirectives(el, state)
// ...
}
在生成AST阶段,也就是parse阶段,v-model被当做普通的指令解析到el.directives中,genDrirectives方法就是遍历el.directives,然后获取每一个指令对应的方法,对于v-model而言,在此处获取的是{name: "model", rawName: "v-model" ...},通过state找到model指令对应的方法model()并执行该方法。
// dev/src/compiler/codegen/index.js line 309
function genDirectives (el: ASTElement, state: CodegenState): string | void {
const dirs = el.directives // 获取指令
if (!dirs) return
let res = 'directives:['
let hasRuntime = false
let i, l, dir, needRuntime
for (i = 0, l = dirs.length; i < l; i++) { // 遍历指令
dir = dirs[i]
needRuntime = true
const gen: DirectiveFunction = state.directives[dir.name] // 对于v-model来说 const gen = state.directives["model"];
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, state.warn)
}
if (needRuntime) {
hasRuntime = true
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
}${
dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
}${
dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
}},`
}
}
if (hasRuntime) {
return res.slice(0, -1) + ']'
}
}
model方法主要是根据传入的参数对tag的类型进行判断,调用不同的处理逻辑。
// dev/src/platforms/web/compiler/directives/model.js line 14
export default function model (
el: ASTElement,
dir: ASTDirective,
_warn: Function
): ?boolean {
warn = _warn
const value = dir.value
const modifiers = dir.modifiers
const tag = el.tag
const type = el.attrsMap.type
if (process.env.NODE_ENV !== 'production') {
// inputs with type="file" are read only and setting the input's
// value will throw an error.
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') {
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
}
genDefaultModel函数先处理了modifiers修饰符,其不同主要影响的是event和valueExpression的值,对于<input>标签event为input,valueExpression为$event.target.value,然后去执行genAssignmentCode去生成代码,以及添加属性值与事件处理。
// dev/src/platforms/web/compiler/directives/model.js line 127
function genDefaultModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
): ?boolean {
const type = el.attrsMap.type
// warn if v-bind:value conflicts with v-model
// except for inputs with v-bind:type
// value与v-model冲突则发出警告
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]
)
}
}
// 修饰符处理
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) {
addHandler(el, 'blur', '$forceUpdate()')
}
}
// dev/src/compiler/directives/model.js line 36
export function genAssignmentCode (
value: string,
assignment: string
): string {
const res = parseModel(value)
if (res.key === null) {
return `${value}=${assignment}`
} else {
return `$set(${res.exp}, ${res.key}, ${assignment})`
}
}
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://cn.vuejs.org/v2/api/#v-model
https://www.jianshu.com/p/19bb4912c62a
https://www.jianshu.com/p/0d089f770ab2
https://cn.vuejs.org/v2/guide/forms.html
https://juejin.im/post/6844903784963899400
https://juejin.im/post/6844903999414485005
https://segmentfault.com/a/1190000021516035
https://segmentfault.com/a/1190000015848976
https://github.com/haizlin/fe-interview/issues/560
https://ustbhuangyi.github.io/vue-analysis/v2/extend/v-model.html
v-model数据绑定分析的更多相关文章
- P,V操作实例分析
刚开始学习操作系统的时候,就听说PV操作,简单说说PV操作. ● P(S): S=S-1 如果S≥0,则该进程继续执行: S<0,进程暂停执行,放入信号量的等待队列 ...
- Java FutureTask<V> 源码分析 Android上的实现
FutureTask类提供了可取消的异步计算,并且可以利用开始和取消计算的方法.查询计算是否完成的方法和获取计算结果的方法. 首先看一下继承关系 public class FutureTask< ...
- yii2 源码分析 model类分析 (五)
模型类是数据模型的基类.此类继承了组件类,实现了3个接口 先介绍一下模型类前面的大量注释说了什么: * 模型类是数据模型的基类.此类继承了组件类,实现了3个接口 * 实现了IteratorAggreg ...
- PE文件加节感染之Win32.Loader.bx.V病毒分析
一.病毒名称:Win32.Loader.bx.V 二.分析工具:IDA 5.5.OllyDebug.StudPE 三.PE文件加节感染病毒简介 PE病毒感染的方式比较多,也比较复杂也比较难分析,下面就 ...
- 中文情感分析 glove+LSTM
最近尝试了一下中文的情感分析. 主要使用了Glove和LSTM.语料数据集采用的是中文酒店评价语料 1.首先是训练Glove,获得词向量(这里是用的300d).这一步使用的是jieba分词和中文维基. ...
- 剖析gcc -v输出
分析gcc -v的详细信息的意义 首先我们需要清楚一点,我们并不能完全弄清楚gcc -v的所有信息,因为毕竟我们并不是GCC编译器集合的实现者,对于这些信息,他们才是最清楚的.由于我们不能将所有的信息 ...
- 不可错过的效能利器「GitHub 热点速览 v.22.39」
如果你是一名前端工程师且维护着多个网站,不妨试试本周榜上有名的 HTML-first 的 Qwik,提升网站访问速度只用一招.除了提升网站加载速度的 Qwik,本周周榜上榜的 Whisper 也是一个 ...
- DBA_Oracle LogMiner分析重做和归档日志(案例)
2014-08-19 Created By BaoXinjian
- YUV和RGB格式分析
做嵌入式项目的时候,涉及到YUV视频格式到RGB图像的转换,虽然之前有接触到RGB到都是基于opencv的处理,很多东西并不需要我们过多深入的去探讨,现在需要完全抛弃现有的算法程序,需要从内存中一个字 ...
随机推荐
- Python测试框架pytest命令行参数用法
在Shell执行pytest -h可以看到pytest的命令行参数有这10大类,共132个 序号 类别 中文名 包含命令行参数数量 1 positional arguments 形参 1 2 gene ...
- C#开发PACS医学影像处理系统(三):界面布局之工具栏
工具栏布局采用WPF中Grid作为容器,按钮采用自定义样式和图标,并采用Separator分割线: XAML设计器代码: 其中 Style="{StaticResource ButtonS ...
- python appium app ui 自动化
1.下载Android-SDK.夜神安卓模拟器.Appium.adb命令执行包并配置环境变量 2.打开夜神 3.打开Appium进行配置,配置如下 4.启动命令,到夜神bin目录下执行 adb con ...
- (课堂笔记)第三章:F5 LTM 负载均衡理论
BIG-IP LTM负载均衡理论 ------F5 BIG-IP LTM负载均衡策略---------- 1.1 LTM VS工作模式F5 BIG-IP LTM的内部对于数据包的处理方式,即是VS的工 ...
- python中unittest参数化
先安装 pip install parameterized 在测试用例类中,@parameterized.parameterized.expand(这里面传一个列表),这个就是参数化的意思
- 算法数据结构 | 三个步骤完成强连通分量分解的Kosaraju算法
强连通分量分解的Kosaraju算法 今天是算法数据结构专题的第35篇文章,我们来聊聊图论当中的强连通分量分解的Tarjan算法. Kosaraju算法一看这个名字很奇怪就可以猜到它也是一个根据人名起 ...
- 移动web开发之rem适配布局
移动web开发之rem适配布局 方案: 页面布局文字能否随着屏幕大小变化而变化 流式布局和flex布局主要针对于宽度布局,那高度如何布局? 怎样让屏幕发生变化的时候元素高度和宽度等比例缩放? 1. r ...
- Magicodes.IE之导入导出筛选器
总体设计 Magicodes.IE是一个导入导出通用库,支持Dto导入导出以及动态导出,支持Excel.Word.Pdf.Csv和Html.在本篇教程,笔者将讲述如何使用Magicodes.IE的 ...
- Java随谈(一)魔术数字、常量和枚举
本文适合对 Java 或 C 有一些了解的用户阅读,推荐阅读时间15分钟. 导言 写这个系列的原因? 我曾经听过一种说法,如果不了解Liunx的网络通讯,就很难理解理解Java的IO:如果不知道Jav ...
- git server“丢失”commit问题探究
1 背景 gitlab某仓库有同事发现部分代码文件内容丢失,具体表现 A. dev分支commit信息是连续的,看不出明显的大时间范围批量丢失 B. 以SuncardCashier/control/C ...