v-model数据绑定分析

v-modelVue提供的指令,其主要作用是可以实现在表单<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并抛出不同的事件:

  • inputtextarea元素使用value propertyinput事件。
  • checkboxradio元素使用checked propertychange事件。
  • 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默认会利用名为valueprop和名为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 idef56410

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) // 生成ASTif (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) returnlet res = 'directives:['let hasRuntime = falselet i, l, dir, needRuntimefor (i = 0, l = dirs.length; i < l; i++) { // 遍历指令dir = dirs[i]needRuntime = trueconst 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 = trueres += `{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 = _warnconst value = dir.valueconst modifiers = dir.modifiersconst tag = el.tagconst type = el.attrsMap.typeif (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 runtimereturn 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 runtimereturn 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 metadatareturn true
}

genDefaultModel函数先处理了modifiers修饰符,其不同主要影响的是eventvalueExpression的值,对于<input>标签eventinputvalueExpression$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数据绑定分析相关推荐

  1. 广义动量定理之速度V的应用分析

    广义动量定理之速度V的应用分析 从广义动量定理Fαt=nmV的角度说,改变速度V,就可以改变成果nmV.速度派以改变速度V作为其主要目的. 速度V应用于兵贵神速 理论简介:三国时期曹操的谋士郭嘉说:& ...

  2. 如何用潜类别混合效应模型(Latent Class Mixed Model ,LCMM)分析老年痴呆年龄数据

    全文下载链接:http://tecdat.cn/?p=24647 线性混合模型假设 N 个受试者的群体是同质的,并且在群体水平上由独特的曲线 Xi(t)β 描述(点击文末"阅读原文" ...

  3. 操作系统实验 P、V原语应用分析

    ** P.V原语应用分析 ** 1.实验原理 (1)Windows操作系统中P操作对应的函数是:WaitForSingleObject(),了解此函数的功能: (2)Windows操作系统中V操作对应 ...

  4. 什么是V Model(V模型)

    v-model是一种软件生存期模型,由Paul Rook在1980年率先提出的,在1990年出现在英国国家计算中心的出版物中,旨在提高软件开发的效率和有效性,是我们熟知的瀑布模型的一种改进,瀑布模型( ...

  5. 微博大V社交圈子分析

    本次分析数据包含了微博中粉丝数排名前1W的用户,以及他们之间的相互关系.这里的关系指的是两人相互关注,如果仅仅是单边关注,则并不包括在内.所以再剔除掉单边关系后,总共有8W条边.得到的分析结果如下所示 ...

  6. 记一次通过v$active_session_history来分析问题的案例

    关于:v$active_session_history是oracle中一个最重要的视图之一,该视图每秒一次记录当前数据库中的活动进程相关的信息,可以用于问题的分析和诊断:同时awr还会将部分v$act ...

  7. 图形渲染技术分享:《GTA V 》图形分析摘要

    环境渲染 最外层的 cubemap 是每一帧实时生成的,目的是简化后续真实反射的渲染. 这个 cubemap 是一张低精度的 128*128 纹理,每个面 30 左右 drawcall,都是地表天空等 ...

  8. php oracle视图,Oracle v$database视图分析

    GUARD_STATUS:防止数据库修改的状态(可能为all或者none或者standby) SUPPLEMENTAL_LOG_DATA_MIN: SUPPLEMENTAL_LOG_DATA_PK: ...

  9. 最新AUTO病毒变种(Win32.Troj.AutoRun.te.v)的分析和解决方案

    病毒全名 Win32.Troj.AutoRun.te.v 病毒长度 89280 威胁级别 ★★ 中文名称 AUTO特务89280 病毒类型 木马下载器 病毒简介 这是一个AUTO病毒.病毒成功运行后, ...

最新文章

  1. 我从500个技术号,选出这10个厉害的推荐给你!
  2. 阿里云华北3超大规模数据中心开服 ECS全系列降价20%
  3. 2021-01-07 matlab数值分析 数值积分与数值微分 复合梯形公式 复合Simpson公式
  4. [zz]一行代码解决iframe挂马(服务器端注入、客户端ARP注入等)
  5. github使用-知乎的某小姐的一篇文章
  6. 它是真实的“盗梦空间”?在这里,一切都可能是数据
  7. websocket心跳检测前后端架构
  8. 《别输在不会表达上》— 综合素质提升书籍
  9. jsonrpc-c编译
  10. tk芯片智能机刷机方法_MTK通用刷机教程 MTK芯片智能机刷机方法
  11. 转载:技术大停滞——范式春梦中的地球工业文明7 寂静星空所隐含的恐怖前景
  12. redis常用命令收集
  13. python清洗数据去除停用词_python之NLP数据清洗
  14. [31期]上班这点事
  15. 李博轩现就职于西部电影集团艺创中心,国家三级摄影师
  16. Vue3零基础学习指南之Vue基础(1)— 模板语法与指令
  17. 第五届新疆省ACM-ICPC程序设计竞赛(重现赛)
  18. 各行业工资单出炉 IT类连续多年霸占“榜首”位置
  19. Linux内核发布时间表
  20. python 不区分大小写的字典实现

热门文章

  1. springMVC从上传的Excel文件中读取数据
  2. Go1.17新特性 ,给我们带来了10%的性能提升
  3. 分页输入框跳转 java_displaytag 分页-添加页码输入框跳转至指定页
  4. YEARWEEK函数来得到本周的日期
  5. 关于JVM垃圾回收的几个问题
  6. scrapy模拟登陆人人网
  7. 携程Apollo分布式配置中心搭建指南
  8. Javascript----input事件实现动态监听textarea内容变化
  9. 确保VDI顺利部署 试点项目是关键
  10. IBM、Google、Oracle三巨头的公有云之殇(下)