proxy and defineProperty

2.1 数据代理的含义

  数据代理的另一个说法是数据劫持,当我们在访问或者修改对象的某个属性时,数据劫持可以拦截这个行为并进行额外的操作或者修改返回的结果。而我们知道Vue响应式系统的核心就是数据代理,代理使得数据在访问时进行依赖收集,在修改更新时对依赖进行更新,这是响应式系统的核心思路。而这一切离不开vue对数据做了拦截代理。两种实现数据代理的方法:Object.definePropertyProxy

2.1.1 Object.defineProperty

官方定义:Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

基本用法:

1、Object.defineProperty(obj, prop, descriptor)

Object.defineProperty()可以用来精确添加或修改对象的属性,只需要在descriptor对象中将属性特性描述清楚,descriptor的属性描述符有两种形式,一种是数据描述符,另一种是存取描述符。

1、数据描述符,它拥有四个属性配置

  • configurable:数据是否可删除,可配置
  • numerable:属性是否可枚举
  • value:属性值,默认为undefined
  • writable:属性是否可读写

需要注意的是: 数据描述符的value,writable和 存取描述符中的get, set属性不能同时存在,否则会抛出异常。有了Object.defineProperty方法,我们可以方便的利用存取描述符中的getter/setter来进行数据的监听,这也是响应式构建的雏形。getter方法可以让我们在访问数据时做额外的操作处理,setter方法使得我们可以在数据更新时修改返回的结果。看看下面的例子,由于设置了数据代理,当我们访问对象o的a属性时,会触发getter执行钩子函数,当修改a属性的值时,会触发setter钩子函数去修改返回的结果。

var o = {}
var value;
Object.defineProperty(o, 'a', {
    get() {
        console.log('获取值')
        return value
    },
    set(v) {
        console.log('设置值')
        value = v
    }
})
o.a = 'sss' 
// 设置值
console.log(o.a)
// 获取值
// 'qqq'

Object.definePropertygetset方法是对对象进行监测并响应变化,那么数组类型是否也可以监测呢,参照监听属性的思路,我们用数组的下标作为属性,数组的元素作为拦截对象,看看Object.defineProperty是否可以对数组的数据进行监控拦截。

var arr = [1,2,3];
arr.forEach((item, index) => {
    Object.defineProperty(arr, index, {
        get() {
            console.log('数组被getter拦截')
            return item
        },
        set(value) {
            console.log('数组被setter拦截')
            return item = value
        }
    })
})
arr[1] = 4;
console.log(arr)
// 结果
数组被setter拦截
数组被getter拦截
4

  显然,已知长度的数组是可以通过索引属性来设置属性的访问器属性的。但是数组的添加确无法进行拦截,这个也很好理解,不管是通过arr.push()还是arr[10] = 10添加的数据,数组所添加的索引值并没有预先加入数据拦截中,所以自然无法进行拦截处理。这个也是使用Object.defineProperty进行数据代理的弊端。为了解决这个问题,Vue在响应式系统中对数组的方法进行了重写,间接的解决了这个问题。

  另外如果需要拦截的对象属性嵌套多层,如果没有递归去调用Object.defineProperty进行拦截,深层次的数据也依然无法监测。

2.1.2 Proxy

  为了解决像数组这类无法进行数据拦截,以及深层次的嵌套问题,es6引入了Proxy的概念,它是真正在语言层面对数据拦截的定义。和Object.defineProperty一样,Proxy可以修改某些操作的默认行为,但是不同的是,Proxy针对目标对象会创建一个新的实例对象,并将目标对象代理到新的实例对象上,本质的区别是后者会创建一个新的对象对原对象做代理,外界对原对象的访问,都必须先通过这层代理进行拦截处理。而拦截的结果是我们只要通过操作新的实例对象就能间接的操作真正的目标对象了。针对Proxy,下面是基础的写法:

var obj = {}
var nobj = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver)
  }
})
nobj.a = '代理'
console.log(obj)
// 结果
设置值
{a: "代理"}

  上面的get,setProxy支持的拦截方法,而Proxy支持的拦截操作有13种之多,具体可以参照ES6-Proxy文档,前面提到,Object.definePropertygettersetter方法并不适合监听拦截数组的变化,那么新引入的Proxy又能否做到呢?我们看下面的例子。

var arr = [1, 2, 3]
let obj = new Proxy(arr, {
  get: function (target, key, receiver) {
      return Reflect.get(target, key, receiver);
  },
  set: function (target, key, receiver) {
      console.log('设置数组');
      return Reflect.set(target, key, receiver);
  }
})
// 1. 改变已存在索引的数据
obj[2] = 3
// result: 设置数组
// 2. push,unshift添加数据
obj.push(4)
// result: 设置数组 * 2 (索引和length属性都会触发setter)
// // 3. 直接通过索引添加数组
obj[5] = 5
// result: 设置数组 * 2
// // 4. 删除数组元素
obj.splice(1, 1)

  显然Proxy完美的解决了数组的监听检测问题,针对数组添加数据,删除数据的不同方法,代理都能很好的拦截处理。另外Proxy也很好的解决了深层次嵌套对象的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值