深圳幻海软件技术有限公司 欢迎您!

不要再搞混Vue的响应式原理和双向数据绑定了

2023-02-28

前言之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue,所以免不了问到其响应式原理和Vue的双向数据绑定。但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别。响应式原理是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
    }  
})
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

我们通过Object.defineProperty为对象obj添加属性,可以设置对象属性的getter和setter函数。之后我们每次通过点语法获取属性都会执行这里的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);
 }
})
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

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

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

双向数据绑定

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

<template>
  <input v-model='localValue'/>
</template>
复制代码
  • 1.
  • 2.
  • 3.
  • 4.

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

<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>
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

因此当我们修改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>
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

上面的组件定了我们通过在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>
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

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

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);
       }
     },
}
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

通过上面的组件定义

<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>
复制代码
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

只不过v-model指令帮我们做上面的事件添加,属性绑定和状态同步操作罢了。这里埋下一个小伏笔,不知道同学们熟不熟悉*Vue的自定义指令*,不熟悉的同学可以在评论区留言,下篇提前把使用自定义指令的各种技巧为各位同学奉上,附带v-model的具体实现。

加更:自定义指令的各种技巧和v-model的实现已完成,感兴趣的同学可以看一下理论+实践,彻底掌握Vue自定义指令

结语

以上就是本人关于Vue响应式原理和双向数据绑定原理的理解