v-model源码深入解析(超详细)

抛出问题

我们先来看一下下面这段代码

<template>
  <div>
    <div class="message">{
  { info.message }}</div>
    <div><input v-model="info.message" type="text"></div>
    <button @click="change">click</button>
  </div>
</template>

<script>
  export default {
    
    data () {
    
      return {
    
        info: {
    }
      }
    },
    methods: {
    
      change () {
    
        this.info.message = 'hello world'
      }
    }
  }
</script>

上述代码很简单,就不做过多的解释了。如果这段代码都看不懂,那下面也没必要再看下去了

问题重现步骤

我现在对上述代码做两种操作:

  1. 一进页面先在输入框中输入hello vue
  2. 一进页面先点击click按钮进行赋值操作,再在输入框中输入hello vue

上述两种情况分别会出现什么现象呢?

第一种操作,当我们在输入框中输入hello vue的时候,class为message的div中会联动出现hello vue,也就是说info中的message属性是响应式的

第二种操作,当我们先进行赋值操作,之后无论在输入框中输入什么内容,class为message的div中都不会联动出现任何值,也就是说info中的message属性非响应式的

问题引发的猜想

查阅vue官方文档我们得知vue在初始化的时候会对data中所有已经定义的对象及其子属性进行遍历,给他们添加gettersetter,使得他们变成响应式的(关于响应式这块之后会单开文章进行解析),但是vue不能检测对象属性的添加或删除。但是,可以使用 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式属性

基于上述描述,我们先看第一种操作.直接在输入框中输入hello vue,class为message的div中会联动出现’hello vue’。但是我们看data中只定义了info对象,其中并没有定义message属性,message属于新增属性。根据vue官方文档中说的,vue不能检测对象属性的添加或删除,所以我猜测vue底层在解析v-model指令的时候,每当触发表单元素的监听事件(例如input事件),就会有Vue.set()操作,从而触发setter

带着这个猜测,我们来看第二种操作。一进页面先点击click按钮,对info.message进行赋值,message属于新增属性,根据官方文档中说的,此时message并不是响应式的,没问题。但是我们接着在input输入框中输入值,class为message的div中没有联动出现任何值,根据我们对于第一种情况的猜测,当输入框监听到input事件的时候,会对info中的message进行Vue.set()操作,所以理论上就算一开始click中是对新增属性message直接赋值的,导致该属性并非响应式的,在经过输入框input事件中的Vue.set()操作之后,应该会变成响应式的,而现在呈现出来的情况并不是这样的啊,这是为什么呢?

聪明的你们应该已经猜到在Vue.set()底层源码中,应该是会判断message属性是否一开始就在info中,如果存在就只是进行单纯的赋值,不存在的话在进行响应式操作,绑定gettersetter

但是光猜测肯定是不够的,我们要用事实说话,做到有理有据。接下来我们就去看下vue源码中v-model这块,看看是不是如我们猜想的一样

探索真相-源码分析

v-model指令使用分为两种情况:一种是在表单元素上使用,另外一种是在组件上使用。我们今天分析的是第一种情况,也就是在表单元素上使用

v-model实现机制

我们先简单说下v-model的机制:v-model会把它关联的响应式数据(如info.message),动态地绑定到表单元素的value属性上,然后监听表单元素的input事件:当v-model绑定的响应数据发生变化时,表单元素的value值也会同步变化;当表单元素接受用户的输入时,input事件会触发,input的回调逻辑会把表单元素value最新值同步赋值给v-model绑定的响应式数据。

v-model实现原理

我用来分析的源码是在vue官网安装模块里面下载的开发版本(2.6.10),便于调试

编译

我们今天讲的内容其实就是把模版编译成render函数的一个流程,这里不会对每步流程都展开讲解,我可以给出一个步骤实现的流程,大家有兴趣的话可以根据这个流程来阅读代码,提高效率
$mount()->compileToFunctions()->compile()->baseCompile()
真正的编译过程都是在这个baseCompile()里面执行,执行步骤可以分为三个过程

  1. 解析模版字符串生成AST
    const ast = parse(template.trim(), options)
  1. 优化语法树
    optimize(ast, options)
  1. 生成代码
    const code = generate(ast, options)

然后我们看下generate里面的代码,这也是我们今天讲的重点

    function generate (
    ast,
    options
  ) {
   
    var state = new CodegenState(options);
    var code = ast ? genElement(ast, state) : '_c("div")';
    return {
   
      render: ("with(this){return " + code + "}"),
      staticRenderFns: state.staticRenderFns
    }
  }

generate() 首先通过 genElement()->genData$2()->genDirectives()生成code,再把 code 用 with(this){return ${code}}} 包裹起来,最终的到render函数。
接下来我们从genDirectives()开始讲解

genDirectives

在模板的编译阶段,v-model跟其他指令一样,会被解析到el.directives中,之后会通过genDirectives方法处理这些指令,我们这里从genDirectives()重点开始讲,至于怎么到这步,如果大家感兴趣的话,可以从generate()开始看

    function genDirectives (el, state) {
   
        var dirs = el.directives;
        if (!dirs) {
    return }
        var res = 'directives:[';
        var hasRuntime = false;
        var i, l, dir, needRuntime;
        for (i = 0, l = dirs.length; i < l; i++) {
   
          dir = dirs[i];
          needRuntime = true;
          var gen = state.directives[dir.name];
          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:\"" + 
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
v-modelVue.js 中常用的一个指令,它可以在表单元素上创建双向数据绑定。在 Vue.js 中,指令是一种特殊的属性,用于添加特定的行为。v-model 指令实际上是一个语法糖,它将表单元素的 value 属性绑定到 Vue 实例中的数据对象的一个属性上,并且在用户输入时自动更新这个属性的值。 下面是一个简单的示例: ```html <input type="text" v-model="message"> ``` 上述代码中,v-model 指令将输入框的值与 Vue 实例中的 message 属性进行双向绑定。当用户在输入框中输入内容时,message 属性的值会自动更新,反之亦然。 接下来,我们来深入分析 v-model源码实现。 首先,我们需要了解 v-model 在编译阶段是如何被解析的。在 Vue.js 中,编译阶段会将模板代码解析为抽象语法树(AST)。当编译器遇到 v-model 指令时,它会将它解析为一个对象: ```javascript { name: 'model', rawName: 'v-model', value: 'message', expression: '"message"', arg: null, modifiers: {} } ``` 在这个对象中,name 属性表示指令的类型,value 属性表示指令绑定的值,expression 属性表示值的表达式,arg 属性表示指令的参数,modifiers 属性表示指令的修饰符。 接下来,编译器会根据指令的类型和绑定的值生成对应的代码,这些代码会在运行时执行,从而实现指令的功能。对于 v-model 指令,编译器会生成以下代码: ```javascript { key: "value", expression: `message`, arg: null, mode: 'twoWay', directive: 'model', modifiers: {} } ``` 这段代码中,key 属性表示绑定的属性名,expression 属性表示绑定的属性值,mode 属性表示数据绑定的模式,directive 属性表示指令的类型,modifiers 属性表示指令的修饰符。 在运行时,Vue.js 会在组件渲染过程中对这些指令进行解析和处理。对于 v-model 指令,Vue.js 会根据指令的模式(单向绑定或双向绑定)生成对应的数据绑定代码。对于单向绑定,Vue.js 会在组件初始化时将属性值赋给表单元素的 value 属性。对于双向绑定,Vue.js 会在表单元素的 input 和 change 事件中更新属性值。 下面是一段简化版的 v-model 指令处理代码: ```javascript function bindModel(el, binding, vnode) { const value = binding.value const mode = binding.mode || 'oneWay' const key = binding.key || 'value' if (mode === 'oneWay') { el.value = value } else if (mode === 'twoWay') { el.value = value el.addEventListener('input', () => { vnode.context[key] = el.value }) } } Vue.directive('model', { bind: bindModel, update: bindModel }) ``` 在这段代码中,bindModel 函数用于处理 v-model 指令。它首先获取指令的 value、mode 和 key 属性,然后根据 mode 属性判断数据绑定的模式。如果是单向绑定,直接将属性值赋给表单元素的 value 属性;如果是双向绑定,同时绑定 input 事件,当用户输入时更新属性值。最后,Vue.js 会将这个指令注册到指令系统中,以便在组件渲染时自动调用。 总的来说,v-model 指令的实现原理其实很简单,就是通过数据绑定来实现表单元素和数据对象之间的双向绑定。但是,在实际开发中,还有很多细节需要注意,比如不同表单元素的处理方式、不同数据类型的转换等等。因此,如果想要深入理解 v-model 指令的实现原理,需要对 Vue.js 的组件渲染机制有比较深入的了解。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值