vue2是用Object.defineProperty()来实现的,vue3是用proxy来实现的.所以本文主要讲Object.defineProperty和proxy的区别

先来浅浅的了解一下Object.defineProperty() 和Proxy:

Object.defineProperty() 

  是在对象上定义一个新属性,或者修改一个对象的现有属性.并返回这个对象.

  语法:Object.defineProperty( 要操纵做的对象 , 要监听的属性名 , 一个包含set/get的操作对象 ) 

  1.试一试,Object.defineProperty()给person对象加一个name属性,并看set和get什么时候触发

let person = {}
let personName = 'lili'

//在person对象上添加属性name,值为personName
Object.defineProperty(person, 'name', {
    get: function () {
        console.log('触发了get方法')
        return personName
    },
    set: function (val) {
        console.log('触发了set方法')
        personName = val
    }
})

基于上述代码,以下是在控制台的操作,以及输出的结果:

 如果要监听一个对象上的所有属性,需要遍历循环整个对象.

 缺陷:循环遍历监听整个对象里的所有属性之后,如果这个对象再加了一个属性,就监听不到了,所以Vue2中对象新增属性的修改需要使用vue.$set来设值。

引申 :$set的原理

  是在vue的原型上添加$set的方法,同时针对以下三种情况情况分别进行处理

  1. 当key已经存在于target上的时候,直接修改target中对应key的值
  2. 当target是数组的时候,借助vue内部拦截处理后数组的splice方法进行赋值,vue对数组的'push','pop','shift','unshift','splice','sort','reverse'方法进行拦截处理成响应式,调用这些方法,可以触发界面的更新,如果只是通过arr[1]=1的方式进行赋值不会触发视图更新。
  3. 对于新增的属性,通过defineReactive把数据转化成getter和setter的方式,并触发数据变化通知

 

Proxy

  Proxy 是一个对象,他用于创建另一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

  语法:const a = new Proxy(target, handler)

      target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

      handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

写一个简单的例子:用 Proxy 构造个叫proxy的对象,(创建了一个代理对象)。

在创建代理对象时,我们定义了一个 get 捕获器,用于捕获属性读取的操作。 

定义了一个 set 捕获器,代理会将所有应用到它的操作转发到这个对象上

const person = {name:"lili"};
const proxy = new Proxy(person, {
  get(target, property, receiver) {
  // target:目标对象。
  // property:被读取的属性名。
// receiver:指向当前的 Proxy 对象或者继承于当前 Proxy 的对象。
    console.log(`触发了get方法,正在访问${property}属性`);
    return target[property];
  },
 set
(target, property, value, receiver) {
  console.log(`触发了set方法,正在给${property}属性设值`);
  target[property] = value
}

});

 基于上述代码,以下是在控制台的操作,以及输出的结果:

由上可见 捕获器的作用就是用于拦截用户对目标对象的相关操作,在这些操作传播到目标对象之前,会先调用对应的捕获器函数,从而拦截并修改相应的行为。

      proxy操作的是proxy实例出来的对象,并不会操作原对象本身。

 

语法中的第二个参数handler对象:支持 13 种捕获器,所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。

在实际的 Proxy 使用场景中,往往会结合 Reflect 对象提供的静态方法来实现某些特定的功能。

-----思考:为什么Proxy和Reflect一起使用呢?

----->因为Proxy和Reflect的方法都是一一对应的,在Proxy里使用Reflect会提高语义化

  还有解决proxy的this指向问题,用Reflect把this放在receiver上,而不放在target

先来看一下proxy的this指向的问题:

const target = {
  checkThis: function () {
    console.log(this === p);
  }
};

const p = new Proxy(target, {});

console.log(target.checkThis)    // false
console.log(p.chceckThis)   // true

在 Proxy 代理的情况下,目标对象内部的this关键字会指向new的proxy对象

在这里来浅浅了解一下Reflect

Reflect:反射,它提供拦截 JavaScript 操作的方法。

这里直接引用了林三心老师在掘金上 《林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析》的解说:

举Reflect两个方法的例子

const person = { name: '林三心', age: 22 }

const proxyPerson = new Proxy(person, {
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
        Reflect.set(target, key, value, receiver)
    }
})

console.log(proxyPerson.name) // 林三心

proxyPerson.name = 'sunshine_lin'

console.log(proxyPerson.name) // sunshine_lin
  • Proxy 中接受的 receiver 形参表示代理对象本身或者继承与代理对象的对象。

  • Reflect 中传递的 receiver 实参表示修改执行原始操作时的 this 指向。

  所以~就知道为什么要尽量把this放在代理对象receiver上,而不建议放原对象target上了:

  因为原对象target有可能本来也是是另一个代理的代理对象,所以如果this一直放target上的话,出bug的概率会大大提高

 

-------------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------

 

 

简单了解完Object.defineProperty和Proxy之后,来看一下区别

区别一:

  Object.defineProperty()作用是在对象上定义一个新属性,或者修改一个对象的现有属性.并返回这个对象.

    通过深层遍历循环整个对象,来达到监听这个对象上所有属性的目的

  监听的是对象的属性

  Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

  代理的是整个对象

思考:由于用Object.defineProperty做监听时,set监听不到数组的push和unshift的变化,所以有的时候操作数组,页面不会变化.但是proxy就没有这个问题,因为它代理的是整个对象

引:vue2中通过改变下标改变数组,也监听不到是为什么呢? 这个并不是Object.defineProperty的问题,而是vue本身考虑性能问题,而没有监听数组的改变下标的这一功能

区别二:

  Object.defineProperty操作的是对象本身

  proxy操作的是proxy实例的对象

 

写在后面:响应式原理是Vue的核心特性之一,数据驱动视图,修改数据视图随之响应更新。

区分与双向数据绑定:

双向数据绑定是vue的一个特性,他只是事件和通过事件对象给state属性赋值的语法糖而已。是指我们使用的v-model指令的实现:vue通过v-model为组件添加上input事件处理和value属性的赋值

<template>
   <input v-model='localValue'/>
</template>
------------------------------------------------------
//上面的v-model就相当于做了如下操作
--------------------------------------------------------
<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>。