v-model的原理

很多同学在理解Vue的时候都把Vue的数据响应原理理解为双向绑定,但实际上这是不准确的,我们之前提到的数据响应,都是通过数据的改变去驱动DOM重新的变化,而双向绑定已有数据驱动DOM外,DOM的变化反过来影响数据,是一个双向关系,在Vue中,我们可以通过v-model来实现双向绑定。

v-model即可以作用在普通表单元素上,又可以作用在组件上,它实际上是一个语法糖,接下来我们就来分析v-model的实现原理。

表单元素

为了更加直观,我们还是结合示例来分析:

 1 let vm = new Vue({
2 el: '#app',
3 template: '<div>'
4 + '<input v-model="message" placeholder="edit me">' +
5 '<p>Message is: {{ message }}</p>' +
6 '</div>',
7 data() {
8 return {
9 message: ''
10 }
11 }
12 })

这是一个非常简单的演示,我们在input元素上设置了v-model属性,绑定了message,当我们在input上输入了内容,message也会同步变化。接下来我们就来分析Vue是如何实现这一效果的,其实非常简单。

也是先从编译阶段分析,首先是parse阶段,v-model被当做普通的指令解析到el.directives中,然后在codegen阶段,执行genData的时候,会执行const dirs = genDirectives(el, state),它的定义在src/compiler/codegen/index.js中:


 1 function genDirectives (el: ASTElement, state: CodegenState): string | void {
2 const dirs = el.directives
3 if (!dirs) return
4 let res = 'directives:['
5 let hasRuntime = false
6 let i, l, dir, needRuntime
7 for (i = 0, l = dirs.length; i < l; i++) {
8 dir = dirs[i]
9 needRuntime = true
10 const gen: DirectiveFunction = state.directives[dir.name]
11 if (gen) {
12 // compile-time directive that manipulates AST.
13 // returns true if it also needs a runtime counterpart.
14 needRuntime = !!gen(el, dir, state.warn)
15 }
16 if (needRuntime) {
17 hasRuntime = true
18 res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
19 dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
20 }${
21 dir.arg ? `,arg:"${dir.arg}"` : ''
22 }${
23 dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
24 }},`
25 }
26 }
27 if (hasRuntime) {
28 return res.slice(0, -1) + ']'
29 }
30 }

genDrirectives方法就是遍历el.directives,然后获取每一个指令对应的方法const gen: DirectiveFunction = state.directives[dir.name],这个指令方法实际上是在实例化CodegenState的时候通过option 引用的,这个option就是编译相关的配置,它在不同的平台下配置不同,在web环境下的定义在src/platforms/web/compiler/options.js下:


 1 export const baseOptions: CompilerOptions = {
2 expectHTML: true,
3 modules,
4 directives,
5 isPreTag,
6 isUnaryTag,
7 mustUseProp,
8 canBeLeftOpenTag,
9 isReservedTag,
10 getTagNamespace,
11 staticKeys: genStaticKeys(modules)
12 }
13 directives定义在src/platforms/web/compiler/directives/index.js中:
14
15
16 export default {
17 model,
18 text,
19 html
20 }
21 // 那么对于v-model而言,对应的directive函数是在src/platforms/web/compiler/directives/model.js中定义的model函数:
22
23 export default function model (
24 el: ASTElement,
25 dir: ASTDirective,
26 _warn: Function
27 ): ?boolean {
28 warn = _warn
29 const value = dir.value
30 const modifiers = dir.modifiers
31 const tag = el.tag
32 const type = el.attrsMap.type
33 ​
34 if (process.env.NODE_ENV !== 'production') {
35 // inputs with type="file" are read only and setting the input's
36 // value will throw an error.
37 if (tag === 'input' && type === 'file') {
38 warn(
39 `<${el.tag} v-model="${value}" type="file">:\n` +
40 `File inputs are read only. Use a v-on:change listener instead.`
41 )
42 }
43 }
44 ​
45 if (el.component) {
46 genComponentModel(el, value, modifiers)
47 // component v-model doesn't need extra runtime
48 return false
49 } else if (tag === 'select') {
50 genSelect(el, value, modifiers)
51 } else if (tag === 'input' && type === 'checkbox') {
52 genCheckboxModel(el, value, modifiers)
53 } else if (tag === 'input' && type === 'radio') {
54 genRadioModel(el, value, modifiers)
55 } else if (tag === 'input' || tag === 'textarea') {
56 genDefaultModel(el, value, modifiers)
57 } else if (!config.isReservedTag(tag)) {
58 genComponentModel(el, value, modifiers)
59 // component v-model doesn't need extra runtime
60 return false
61 } else if (process.env.NODE_ENV !== 'production') {
62 warn(
63 `<${el.tag} v-model="${value}">: ` +
64 `v-model is not supported on this element type. ` +
65 'If you are working with contenteditable, it\'s recommended to ' +
66 'wrap a library dedicated for that purpose inside a custom component.'
67 )
68 }
69 ​
70 // ensure runtime directive metadata
71 return true
72 }

也就是说我们执行needRuntime = !!gen(el, dir, state.warn)就是在执行model函数,它会根据AST元素例程的不同情况去执行不同的逻辑,对于我们这个案例而言,它会命中genDefaultModel(el, value, modifiers)的逻辑,稍后我们也会介绍组件的处理,其他分开同学们可以自行去看。我们来看一下genDefaultModel的实现:

 1 function genDefaultModel (
2 el: ASTElement,
3 value: string,
4 modifiers: ?ASTModifiers
5 ): ?boolean {
6 const type = el.attrsMap.type
7 ​
8 // warn if v-bind:value conflicts with v-model
9 // except for inputs with v-bind:type
10 if (process.env.NODE_ENV !== 'production') {
11 const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value']
12 const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']
13 if (value && !typeBinding) {
14 const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
15 warn(
16 `${binding}="${value}" conflicts with v-model on the same element ` +
17 'because the latter already expands to a value binding internally'
18 )
19 }
20 }
21 ​
22 const { lazy, number, trim } = modifiers || {}
23 const needCompositionGuard = !lazy && type !== 'range'
24 const event = lazy
25 ? 'change'
26 : type === 'range'
27 ? RANGE_TOKEN
28 : 'input'
29 ​
30 let valueExpression = '$event.target.value'
31 if (trim) {
32 valueExpression = `$event.target.value.trim()`
33 }
34 if (number) {
35 valueExpression = `_n(${valueExpression})`
36 }
37 ​
38 let code = genAssignmentCode(value, valueExpression)
39 if (needCompositionGuard) {
40 code = `if($event.target.composing)return;${code}`
41 }
42 ​
43 addProp(el, 'value', `(${value})`)
44 addHandler(el, event, code, null, true)
45 if (trim || number) {
46 addHandler(el, 'blur', '$forceUpdate()')
47 }
48 }

genDefaultModel函数先处理了modifiers,它的不同主要影响的是eventvalueExpression的值,对于我们的示例,eventinputvalueExpression$event.target.value。然后去执行genAssignmentCode去生成代码,它的定义在src/compiler/directives/model.js中:

 1 /**
2 * Cross-platform codegen helper for generating v-model value assignment code.
3 */
4 export function genAssignmentCode (
5 value: string,
6 assignment: string
7 ): string {
8 const res = parseModel(value)
9 if (res.key === null) {
10 return `${value}=${assignment}`
11 } else {
12 return `$set(${res.exp}, ${res.key}, ${assignment})`
13 }
14 }

该方法首先对对应v-model的精心解决value,它处理了非常多的情况,对我们的例子,value就是messgae,所以返回的res.keynull,然后我们就得到${value}=${assignment},也就是message=$event.target.value。然后我们又命中了needCompositionGuard为true的逻辑,所以最终的codeif($event.target.composing)return;message=$event.target.value

code 生成完后,又执行了2句非常关键的代码:

1 addProp(el, 'value', `(${value})`)
2 addHandler(el, event, code, null, true)

这实际上就是input实现v-model的精髓,通过修改AST元素,给el添加一个prop,相当于我们在input上动态绑定了value,又给el添加了事件处理,相当于在input上绑定了input事件,实际上转换成模板如下:


1 <input
2 v-bind:value="message"
3 v-on:input="message=$event.target.value">

其实就是动态绑定了inputvalue指向messgae变量,并且在触发input事件的时候去动态把message设置为目标值,这样实际上就完成了数据双向绑定了,所以说v-model实际上就是语法糖。

再回到genDirectives,它接下来的逻辑就是根据指令生成一些data的代码:


 1 if (needRuntime) {
2 hasRuntime = true
3 res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
4 dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
5 }${
6 dir.arg ? `,arg:"${dir.arg}"` : ''
7 }${
8 dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
9 }},`
10 }

对我们的例子而言,最终生成的render代码如下:

 1 with(this) {
2 return _c('div',[_c('input',{
3 directives:[{
4 name:"model",
5 rawName:"v-model",
6 value:(message),
7 expression:"message"
8 }],
9 attrs:{"placeholder":"edit me"},
10 domProps:{"value":(message)},
11 on:{"input":function($event){
12 if($event.target.composing)
13 return;
14 message=$event.target.value
15 }}}),_c('p',[_v("Message is: "+_s(message))])
16 ])
17 }

关于事件的处理我们之前的章节已经分析过了,所以对于inputv-model而言,完全就是语法糖,并且对于其他表单元素套路都是一样,区别在于生成的事件代码会略有不同。

v-model 除了作用在表单元素上,新版的Vue还把这一语法糖用在了组件上,接下来我们来分析它的实现。

本文转自 http://caibaojian.com/vue-analysis/extend/v-model.html

v-model原理问题的更多相关文章

  1. DPM(Deformable Parts Model)--原理(一)(转载)

    DPM(Deformable Parts Model) Reference: Object detection with discriminatively trained partbased mode ...

  2. DPM(Deformable Parts Model)--原理(一)

    http://blog.csdn.net/ttransposition/article/details/12966521 DPM(Deformable Parts Model) Reference: ...

  3. miaov- 自动生成正V反V大于号V小于号V楼梯等图案

    1. 核心:控制 数量的长度-1-i的位置,是放在left上还是top上?是放在前面还是后面! <!DOCTYPE html> <html lang="en"&g ...

  4. 使用RStudio调试(debug)基础学习(二)和fGarch包中的garchFit函数估计GARCH模型的原理和源码

    一.garchFit函数的参数--------------------------------------------- algorithm a string parameter that deter ...

  5. 什么是V模型?使用SDLC和STLC学习案例研究

    本教程详细介绍了软件/系统开发生命周期(SDLC),如瀑布循环和迭代循环,如RAID和Agile.此外,它继续解释测试的V模型和STLC(软件测试生命周期). 假设为您分配了一项任务,即为客户开发自定 ...

  6. 不可错过的效能利器「GitHub 热点速览 v.22.39」

    如果你是一名前端工程师且维护着多个网站,不妨试试本周榜上有名的 HTML-first 的 Qwik,提升网站访问速度只用一招.除了提升网站加载速度的 Qwik,本周周榜上榜的 Whisper 也是一个 ...

  7. vue.js初级入门之最基础的双向绑定操作

    首先在页面引入vue.js以及其他需要用到的或者可能要用到的插件(这里我多引用了bootstrap和jquery) 引用的时候需要注意文件的路径,准备工作这样基本就完成了,下面正式开始入门. vue. ...

  8. backbone学习总结(二)

    今天来看下backbone的路由控制的功能.其实个人感觉backbone,模块就那么几个,熟悉它的框架结构,以及组成,就差不多. 废话不多说,我们来看看还剩下的功能. 关于路由和历史管理 通过 Bac ...

  9. backbone学习总结(一)

    入职第三天,新公司项目用到backbone+underscore+require等框架,前两天把项目的开发环境都配置好啦,项目也能跑起来,现在准备好好学习公司自己的框架以及用到的框架,有点想吐槽,开发 ...

  10. Backbone学习总结

    Backbone中文学习文档:http://www.css88.com/doc/backbone/ 来到公司已经有一段时间了,到现在深深的感觉到自己的能力弱的像只周黑鸭,又干涩又黝黑,充满了麻(手麻脑 ...

随机推荐

  1. 767. Reorganize String - LeetCode

    Question 767. Reorganize String Solution 题目大意: 给一个字符串,将字符按如下规则排序,相邻两个字符一同,如果相同返回空串否则返回排序后的串. 思路: 首先找 ...

  2. unity---光照基础

    发射光源类型 光照参数介绍 让摄像头看到Flare 耀斑 改变影子

  3. 「POI2012」井 Well

    description 你要挖井,\(n\)个地面的高度可视为\(h_i\),每次操作你可以将一个\(h_i-1\),你最多可执行\(m\)次操作. 你要任选其中一个\(h_i\)挖到\(0\),问你 ...

  4. dotnet-cnblog-tool 图片上传失败问题

    dotnet-cnblog-tools 这个工具是将本地的 Markdown 文件转换为 可以上传到 cnblog 的格式,并且会将图片自动上传到 cnblog 的图床. 具体可以参考这篇文章: cn ...

  5. 使用Rclone将Onedirve挂载到Linux本地

    1. centos挂载onedrive时, 需要安装fuse. # 安装fuse yum -y install fuse 2. 安装完fuse后使用rclone进行挂载 #创建挂载目录 mkdir - ...

  6. Servlet的本质

    简介:Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层. 功能:使 ...

  7. VmWare安装Centos8注意事项

    VmWare安装Centos8注意事项 1.需选择稍后安装操作系统 2.选择操作系统版本 3.修改虚拟机配置 4.配置完成点击开启虚拟机(注意要将鼠标放在屏幕中央,点击一下后才能使用上下键进行选择) ...

  8. 聊聊C#中的composite模式

    写在前面 Composite组合模式属于设计模式中比较热门的一个,相信大家对它一定不像对访问者模式那么陌生,毕竟谁又没有遇到过树形结构呢.不过所谓温故而知新,我们还是从一个例子出发,起底一下这个模式吧 ...

  9. NLog自定义Target之MQTT

    NLog是.Net中最流行的日志记录开源项目(之一),它灵活.免费.开源 官方支持文件.网络(Tcp.Udp).数据库.控制台等输出 社区支持Elastic.Seq等日志平台输出 实时日志需求 在工业 ...

  10. 一篇文章讲清楚MySQL的聚簇/联合/覆盖索引、回表、索引下推

    迎面走来了你的面试官,身穿格子衫,挺着啤酒肚,发际线严重后移的中年男子. 手拿泡着枸杞的保温杯,胳膊夹着MacBook,MacBook上还贴着公司标语:"加班使我快乐". 面试官: ...