Vue响应式原理及双向数据绑定示例分析

目录
  • 前言
  • 响应式原理
  • 双向数据绑定

前言

之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue,所以免不了问到其响应式原理和Vue的双向数据绑定。但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别。

响应式原理

是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,就很优雅~

Vue2.x是借助Object.defineProperty()实现的,而Vue3.x是借助Proxy实现的,下面我们先来看一下2.x的实现。

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    //拦截get,当我们访问data.key时会被这个方法拦截到
    get: function getter () {
        //我们在这里收集依赖
        return obj[key];
    },
    //拦截set,当我们为data.key赋值时会被这个方法拦截到
    set: function setter (newVal) {
        //当数据变更时,通知依赖项变更UI
    }
})

我们通过Object.defineProperty为对象obj添加属性,可以设置对象属性的gettersetter函数。之后我们每次通过点语法获取属性都会执行这里的getter函数,在这个函数中我们会把调用此属性的依赖收集到一个集合中 ;而在我们给属性赋值(修改属性)时,会触发这里定义的setter函数,在次函数中会去通知集合中的依赖更新,做到数据变更驱动视图变更。

3.x的与2.x的核心思想一致,只不过数据的劫持使用Proxy而不是Object.defineProperty,只不过Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。

let nObj=new Proxy(obj,{
  //拦截get,当我们访问nObj.key时会被这个方法拦截到
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  //拦截set,当我们为nObj.key赋值时会被这个方法拦截到
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
})

Proxy的详细使用方法参考ES6教程

Vue的响应式原理的实现细节相信大多数同学已经很熟悉了,这里就不在展开细谈了,如果还想更详细的了解,或者想要做一个简易的Vue实现,可以参考这篇Vue原理,相信你会有不小收获。

双向数据绑定

双向数据绑定通常是指我们使用的v-model指令的实现,是Vue的一个特性,也可以说是一个input事件和value的语法糖。 Vue通过v-model指令为组件添加上input事件处理和value属性的赋值。

<template>
   <input v-model='localValue'/>
</template>

上述的组件就相当于如下代码

<template>
   <!-- 这里添加了input时间的监听和value的属性绑定 -->
   <input @input='onInput' :value='localValue' />
   <span>{{localValue}}</span>
</template>
<script>
  export default{
    data(){
      return {
        localValue:'',
      }
    },
    methods:{
      onInput(v){
         //在input事件的处理函数中更新value的绑定值
         this.localValue=v.target.value;
         console.log(this.localValue)
      }
    }
  }
</script>
    <template>
      <div>
        <input @input='onInput' :value='localValue' />
        <span>{{localValue}}</span>
      </div>
    </template>
    <script>
    // import Vue from 'vue';
    export default{
      data(){
        return {
          localValue:'hello',
        }
      },
      methods:{
        onInput(v){
          this.localValue=v.target.value;
          console.log(this.localValue)
        }
      }
    }
    </script>
    <style>
    .count {
      color: red;
    }
    </style>

因此当我们修改input输入框中的值时,我们通过v-model绑定的值也会同步修改,基于上述原理,我们可以很容易的实现一个数据双向绑定的组件。

v-model实践

首先我们定义一个Vue组件,相信大家已经很熟悉了。

<tempalte>
  <div class="count" @click="addCount">click me {{value}}</div>
</template>
<script>
export default{
      props:{
       //关键的第一步:设置一个value属性
        value:{
          type:Number,
          default:0
        }
      },
      watch:{
        //监听value变化,更新组件localvalue状态
        value(v){
          this.localvalue=v;
        }
      },
      methods:{
        //关键的第二步:事件触发localvalue变更,通过事件同步父组件状态变更
        addCount(){
           this.localvalue++;
           this.$emit('input',this.localvalue);
        }
      },
      data(){
        return{
          //组件状态,遵守单项数据流原则,不直接修改props中的属性
          localvalue:0
        }
      },
      created(){
        //初始化获取value值
        this.localvalue=this.value;
      }
    }
</script>

上面的组件定了我们通过在props中添加value属性,并且在值更新时触发input事件。created钩子和watch中为localvalue赋值是为了同步父组件状态到子组件中。
通过上面的组件定义,我们就可以在组件上使用v-model指令做双向数据绑定了。

<template>
  <add-one v-model="count"></add-one>
  <span>父组件{{count}}</span>
</tempalte>
<script>
export default{
  data() {
    return {
       count: 0,
    };
  },
  methods: {
  },
  created(){
  }
}
</script>

下面是实际效果

import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
    Vue.directive('mymodel', {
        bind(el, binding, vnode, oldVnode) {
            //组件和原生input标签需要分别处理,
            el.value = binding.value;
            if (vnode.tag == 'input') {
                //监听绑定的变量
                vnode.context.$watch(binding.expression, (v) => {
                    el.value = v;
                })
                //添加inout事件监听
                el.addEventListener('input', (e) => {
                    //context是input所在的父组件,这一步是同步数据
                    vnode.context[binding.expression] = e.target.value;
                })
            } else { //组件
                //vnode的结构可以参见文档。不过我觉得最直观的方法就是直接在控制台打印处理
                let {
                    componentInstance,
                    componentOptions,
                    context
                } = vnode;
                const {
                    _props
                } = componentInstance;
                //处理model选项
                if (!componentOptions.Ctor.extendOptions.model) {
                    componentOptions.Ctor.extendOptions.model = {
                        value: 'value',
                        event: 'input'
                    }
                }
                let modelValue = componentOptions.Ctor.extendOptions.model.value;
                let modelEvent = componentOptions.Ctor.extendOptions.model.event;
                //属性绑定,这里直接修改了属性,没有想到更好的办法,友好的意见希望可以提出
                console.log(binding)
                _props[modelValue] = binding.value;
                context.$watch(binding.expression, (v) => {
                    _props[modelValue] = v;
                })
                //添加事件处理函数,做数据同步
                componentInstance.$on(modelEvent, (v) => {
                    context[binding.expression] = v;
                })
            }
        },
        inserted() {},
        update() {},
        componentUpdated() {},
        unbind() {},
    })
    Vue.component('add-one', {
        template: '<div class="count" @click="addCount">click me {{localvalue}}</div>',
        props: {
            value: {
                type: Number | String,
                default: -1
            }
        },
        //自定义value和事件
        // model: {
        //     value: 'count',
        //     event: 'change'
        // },
        watch: {
            //监听value变化,更新组件localvalue状态
            value(v) {
                this.localvalue = v;
            }
        },
        methods: {
            //事件触发localvalue变更,通过事件同步父组件状态变更
            addCount() {
                this.localvalue++;
                this.$emit('input', this.localvalue);
            }
        },
        data() {
            return {
                localvalue: 0
            }
        },
        created() {
            //初始化获取value值
            console.log(this.value)
            this.localvalue = this.value;
        }
    })
    new Vue({
        el: '#app',

        data() {
            return {
                count: 0,
            };
        },
        methods: {},
        created() {

        }
    })

当然我们也可以不使用valueinput事件这样的组合,为了更使得组件的定义更加符合语义,我们也可以自定义要实现双向绑定的属性和事件。 我们在组件的model选项中设置valueevent即可。如下:

export default{
      //这里做了一个value和event的映射
      model:{
        value:'count',
        event:'change'
      },
      props:{
       //关键的第一步:设置一个value属性
        count:{
          type:Number,
          default:0
        }
      },
      methods:{
        //关键的第二步:事件触发localvalue变更,通过事件同步父组件状态变更
        addCount(){
           this.localvalue++;
           this.$emit('change',this.localvalue);
        }
      },
}

通过上面的组件定义

<add-one v-model="count"></add-one>

就相当于

<template>
   <add-one @change='onChange' :count='count'></add-one>
   <span>{{count}}</span>
</template>
<script>
  export default{
    data(){
      return {
        count:0,
      }
    },
    methods:{
      onChange(v){
         this.count=v;
         console.log(this.count)
      }
    }
  }
</script>

只不过v-model指令帮我们做上面的事件添加,属性绑定和状态同步操作罢了。

以上就是Vue响应式原理及双向数据绑定示例分析的详细内容,更多关于Vue响应式双向数据绑定的资料请关注我们其它相关文章!

(0)

相关推荐

  • 图解Vue 响应式流程及原理

    目录 阅读本文能够帮助你什么? 一.组件化流程 1. 整个new Vue阶段做了什么? 2. 普通dom元素如何渲染到页面? 3. 组件如何渲染到页面? 4. Vue组件化简化流程 二.响应式流程 1. 依赖收集 2. 派发更新 三.彩蛋篇 1. computed依赖收集 2. computed派发更新 3. user Watcher依赖收集 阅读本文能够帮助你什么? 在学习vue源码的时候发现组件化过程很绕? 在响应式过程中Observer.Dep.Watcher三大对象傻傻分不清? 搞不清楚

  • Vue3 响应式系统实现 computed

    目录 前言 实现 computed 总结 前言 上篇文章我们实现了基本的响应式系统,这篇文章继续实现 computed. 首先,我们简单回顾一下: 响应式系统的核心就是一个 WeakMap --- Map --- Set 的数据结构. WeakMap 的 key 是原对象,value 是响应式的 Map.这样当对象销毁的时候,对应的 Map 也会销毁. Map 的 key 就是对象的每个属性,value 是依赖这个对象属性的 effect 函数的集合 Set.然后用 Proxy 代理对象的 ge

  • vue2与vue3双向数据绑定的区别说明

    目录 vue2与vue3双向数据绑定区别 Vue2双向数据绑定存在的问题 原理 vue3.0双向数据绑定Proxy vue语法:数据的双向绑定 1.指令v-model 2.v-model与修饰符 3.v-model与自定义组件 vue2与vue3双向数据绑定区别 新的响应式机制采用了ES6的ProxyApi,抛弃了Object.defineProperty() Vue2双向数据绑定存在的问题 关于对象: Vue 无法检测 property 的添加或移除.由于 Vue 会在初始化实例时对prope

  • Vue.js3.2响应式部分的优化升级详解

    目录 背景 响应式实现原理 依赖收集 派发通知 副作用函数 响应式实现的优化 依赖收集的优化 响应式 API 的优化 trackOpBit 的设计 总结 背景 Vue 3 正式发布距今已经快一年了,相信很多小伙伴已经在生产环境用上了 Vue 3 了.如今,Vue.js 3.2 已经正式发布,而这次 minor 版本的升级主要体现在源码层级的优化,对于用户的使用层面来说其实变化并不大.其中一个吸引我的点是提升了响应式的性能: More efficient ref implementation (~

  • Vue2.0/3.0双向数据绑定的实现原理详解

    Vue2.0/3.0 双向数据绑定的实现原理 双向数据绑定简意 即数据的改变能让页面重新渲染 Vue2.0 ES5的原理: Object.defineProperty 对数据进行拦截 简单小案例 <body> 姓名: <span id="name"></span> <br /> <input type="text" id="inputName" /> </body> 改变in

  • Vue3 Reactive响应式原理逻辑详解

    目录 前言 一.怎么实现变量变化 二.怎么实现变量变化 三.将多个dep存储在Map中 四.将多个object的depsMap继续存储起来 五.核心 六.源码解析(TypeScript) 前言 本篇文章主要讲解vue响应式原理的逻辑,也就是vue怎么从最开始一步步推导出响应式的结构框架. 先从头构建一个简单函数推导出Vue3的Reactive原理,最后再进行源码的验证. 一.怎么实现变量变化 怎么实现变量变化,相关依赖的结果也跟着变化 当原本price=5变为price=20后total应该变为

  • Vue响应式原理及双向数据绑定示例分析

    目录 前言 响应式原理 双向数据绑定 前言 之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue,所以免不了问到其响应式原理和Vue的双向数据绑定.但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别. 响应式原理 是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,就很优雅~ Vue2.x是借助Object.defineProperty()实现的,而Vue3.x是借助

  • vue响应式原理与双向数据的深入解析

    了解object.defineProperty 实现响应式 清楚 observe/watcher/dep 具体指的是什么 了解 发布订阅模式 以及其解决的具体问题 在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是: 对象属性拦截 (vue2.x) Object.defineProperty 对象整体代理 (vue3.x) Proxy 提示:以下是本篇文章正文内容,下面案例可供参考 vue-响应式是什么? Vue 最独特的特性之一

  • Vue响应式原理的示例详解

    Vue 最独特的特性之一,是非侵入式的响应系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.聊到 Vue 响应式实现原理,众多开发者都知道实现的关键在于利用 Object.defineProperty , 但具体又是如何实现的呢,今天我们来一探究竟. 为了通俗易懂,我们还是从一个小的示例开始: <body> <div id="app"> {{ message }} </div> <script> v

  • 详细分析vue响应式原理

    前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它们有助于理解流程 将流程拆分,理解其中的作用 结合以上的点,理解整体流程 文章稍长,但大部分是代码实现,还请耐心观看.为了方便理解原理,文中的代码会进行简化,如果可以请对照源码学习. 主要成员 响应式原理中,Observe.Watcher.Dep这三个类是构成完整原理的主要成员. Observe,响

  • Vue响应式原理模拟实现原理探究

    目录 前置知识 数据驱动 数据响应式的核心原理 Vue 2.x Vue 3.x 发布订阅和观察者模式 发布/订阅模式 观察者模式 Vue响应式原理模拟实现 Vue Observer对data中的属性进行监听 Compiler Watcher Dep 测试代码 前置知识 数据驱动 数据响应式——Vue 最标志性的功能就是其低侵入性的响应式系统.组件状态都是由响应式的 JavaScript 对象组成的.当更改它们时,视图会随即自动更新. 双向绑定——数据改变,视图改变:视图改变,数据也随之改变 数据

  • Vue响应式原理Observer、Dep、Watcher理解

    开篇 最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的.自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果

  • 一篇文章带你彻底搞懂VUE响应式原理

    目录 响应式原理图 编译 创建compile类 操作fragment 获取元素节点上的信息 获取文本节点信息 操作fragment 响应式 数据劫持 收集依赖 响应式代码完善 Dep类 全局watcher用完清空 依赖的update方法 需要注意的一个地方 双剑合璧 总结 首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重. 响应式原理图 一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程.

  • Vue响应式原理深入解析及注意事项

    前言 Vue最明显的特性之一便是它的响应式系统,其数据模型即是普通的 JavaScript 对象.而当你读取或写入它们时,视图便会进行响应操作.文章简要阐述下其实现原理,如有错误,还请不吝指正.下面话不多说了,来随着小编来一起学习学习吧. 响应式data <div id = "exp">{{ message }}</div> const vm = new Vue({ el: '#exp', data: { message: 'This is A' } }) vm

  • 详解VUE响应式原理

    目录 1.响应式原理基础 2.核心对象:Dep与Watcher 3.收集依赖与更新依赖 3.1 收集依赖 3.2 更新依赖 4.源码调试 4.1 测试的页面代码 1.对象说明 2.Dep与Watcher的关系 3.最终的关系结果 4.2  源码调试 1.收集依赖的入口函数:initState(页面初始化时执行); 2.初始化computed和watch时,生成Watcher实例化对象 总结 1.响应式原理基础 响应式基本原理是基于Object.defineProperty(obj, prop,

  • Vue响应式原理详解

    Vue 嘴显著的特性之一便是响应式系统(reactivity system),模型层(model)只是普通JavaScript对象,修改它则更新视图(view). Vue 响应式系统的底层细节 如何追踪变化 把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象的所有属性,并使用Object.defineProperty 把这些属性全部转为 getter/setter.Object.defineProperty是仅ES5支持,并无法shim的特性,这也就是为什么Vu

随机推荐