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

本文深入探讨了Vue.js中v-model指令的工作原理,通过问题提出、重现步骤揭示了v-model在响应式系统中的行为。通过对源码的分析,解析了v-model的实现机制和原理,包括编译过程、model方法、genDirectives、genAssignmentCode等关键环节,解答了为何在特定情况下v-model不会使新属性变为响应式的问题。同时,还提到了不同Vue版本中v-model对未定义属性响应化支持的差异。
摘要由CSDN通过智能技术生成

抛出问题

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

<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:\"" + 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值