Object.defineProperty/Proxy的使用与vue2/vue3如何使用响应式

85 阅读4分钟

Object.defineProperty

该方法是ES5引入的方法,可以在一个对象上新增一个属性或者直接修改某个属性。该方法接收三个参数

  • 某对象
  • 要定义或修改的属性的名称或 Symbol
  • 要定义或修改的属性描述符。

以下是最简易的调用方式:

    const obj = Object.defineProperty({},'p',{}) 
    console.log(obj) // {p: undefined}

属性描述符

属性描述符的种类主要有两种

  1. 数据描述符
  2. 存取描述符

其实所谓的数据描述符和存取描述符指的都是传递给defineProperty方法第三个参数中对象的属性。

代表数据描述符的属性

  1. value 属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为undefined。
  2. writable 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符变。 默认为 false

如:

const obj = {}
Object.defineProperty(obj,'p',{
 value: 'property',
 writable: true
})
console.log(obj) //{p: 'property'}
obj.p = 'newProperty'
console.log(obj) //{p: 'newProperty'}

代表存取描述符的属性

  1. get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象。该函数的返回值会被用作属性的值。
  2. set 属性的 setter 函数,如果没有 setter,则为 undefined。 当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。 默认为undefined。 如

image.png

既可以代表数据描述符又可以代表存取描述符的属性

  1. configurable 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false
  2. enumerable 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false。 如
const obj = {}
Object.defineProperty(obj,'newP',{
  configurable  : true, //这里让属性描述符可改变
  writable: true
})
 obj.newP = 'setP';
 console.log(obj.newP) //setP
 Object.defineProperty(obj,'newP',{
  writable: false 
})
obj.newP = 'wP'
console.log(obj.newP)//这里输入setP说明属性描述符被改变了

数据描述符和存取描述符不可共存,如

image.png

vue2的响应式主要是通过Object.defineProperty实现的,其过程如下

  1. vue实例化的时候,会遍历data的所有属性,为其添加getter和setter
  2. 当读取数据的时候,getter会被触发,vue2会将其加入依赖收集器当中
  3. 当给数据赋值的时候,setter会被触发,vue2会在setter中通知依赖收集器更新

Proxy

Proxy是ES6提供的新对象,可以通过它代理对象,并且拦截该对象的所有操作,其只能被new关键字调用,如

new Proxy(target, handler)

其中

  1. target指的是被代理的对象,它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。
  2. handler同样是对象,它的属性提供了某些操作发生时所对应的处理函数。比较常用的有get和set

handler get

handler.get() 方法用于拦截对象的读取属性操作。其会拦截的情况有以下三种:

  1. 访问属性:proxy[foo] 和``proxy.bar
  2. 访问原型链上的属性:Object.create(proxy)[foo]
  3. Reflect.get()

get接收三个参数,target,prop,receiver

其中

  1. target为被代理的对象
  2. prop为被捕获的属性名
  3. Proxy或者继承Proxy的对象(即包装代理对象的对象),如

image.png

get返回值同样会被当成属性的值

image.png

get约束

  1. 如果要访问的目标属性是不可写(writable false)并且不可配置的(configurable false),则返回的值必须与该目标属性的值相同。如

image.png

handler set

handler.set() 方法是设置属性值操作的捕获器。其会拦截得情况有以下三种

  1. 指定属性值:proxy[foo] = bar 和 proxy.foo = bar
  2. 指定继承者的属性值:Object.create(proxy)[foo] = bar
  3. Reflect.set()

handler接收4个参数,target,prop,value,receiver

其中

  1. target为被代理的对象
  2. prop为被捕获的属性名
  3. value为新设置的值
  4. Proxy或者继承Proxy的对象(即包装代理对象的对象),如

image.png

set() 方法应当返回一个布尔值。(在非严格模式下似乎不返回也没什么问题)

  1. 返回 true 代表属性设置成功。
  2. 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。

set约束

若目标属性是一个不可写及不可配置的数据属性,则不能改变它的值。如

image.png

vue3就是利用Proxy实现响应式

  1. 在vue3中当初始化一个vue实例的时候,对创建一个Proxy对象,拦截对数据的所有操作
  2. 在访问数据的时候,代理会收集当前的响应式依赖(如通过get拦截)
  3. 在操作数据时,代理会通知响应式依赖进行更新(如通过set拦截)