Proxy和Reflect的爱恨情仇

说到 vue3, 总要提到 Proxy, 因为这是vue3较vue2的重要更新/优化内容之一, 好嘛, 这还算能理解, 但最近发现还有一货叫做Reflect, 这又是啥?? 今天就把这俩货捋一捋呗(边查资料边写, 本人的理解或有偏颇, 若有错漏欢迎指出)

打开MDN瞧它一瞧

Proxy(ES6)

image.png

简单说就是, 把一个创建的对象传入proxy, 同时把handler传入, 便可通过handler里面定义的方法, 监听对象的操作, 包括: _get, set, apply, defineProperty, deleteProperty_等等, 示例如下:

Tips :

  • 这里列出了handler的所有属性方法, 可以只看’get’和’set’,直接跳到到后面看调用
  • 也可copy直接调试
  • handler属性的定义使用省略属性名的写法, 其方法名即为属性名
const foo = {
  user :{},
  otherInfo: 'other'
}
// handler的全部属性如下,可以只看'get'和'set',其他先跳过
const handler = {
  // @params: target- 目标对象
  // @params: property- 将被操作的属性名或Symbol
  // @params: value- 新属性值,用于set
  // @params: receiver- Proxy或者继承Proxy的对象
  get(target,property,receiver){
      // 相当于可监控所有属性的读取操作
      console.log('get')
      // 通过return返回默认行为值或任意值
      return property in target ? target[property] : undefined
  },
  set(target, property, value, receiver){
      //读取属性会调用set(),但是需要在本函数中进行赋值,否则该属性不会更新到原对象
      console.log('set')
      target[property] = value
  },
  getPrototypeOf(target) {
      // 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
      // 返回一个对象或null
      return Object.getPrototypeOf(target);
  },
  setPrototypeOf(target, prototype) {
      return Object.setPrototypeOf(...arguments)
  }, 
  // @params: thisArg- 被调用时的上下文对象,即this
  // @params: argumentsList- 被调用时的参数数组
  apply(target, thisArg, argumentsList) {
      // 拦截函数的调用
      return target(...argumentsList)
  },
  has(target,property){
      console.log('has')
      return property in target
  },
  // @params: descriptor- 待定义或修改的属性的描述
  defineProperty(target, property, descriptor) {
      // 拦截对对象的 Object.defineProperty() 操作
      console.log('defineProperty')
      Object.defineProperty(...arguments)
      // 必须返回一个Boolean以标志是否操作成功
      return true
  },
  deleteProperty(target, property) {
      // 拦截对象属性的delete操作
      // 必须返回一个Boolean以标志是否操作成功
      return true
  },
  getOwnPropertyDescriptor(target, property) {
      return Object.getOwnPropertyDescriptor(...arguments)
  },
  isExtensible(target) {
      return Object.isExtensible(...arguments)
  },
  ownKeys(target) {
      // 用于拦截 Reflect.ownKeys() 或 Object.getOwnPropertyNames()
      return ['a', 'b', 'c'];
  },
  preventExtensions(target) {
      // 对Object.preventExtensions()的拦截,返回布尔值
      return true
  }
}

//使用方法:
const p = new Proxy(foo, handler)
console.log(p.otherInfo) 
// > get
// > other

p.otherInfo = 'something else'
console.log(p.otherInfo) 
// > set
// > get
// > something else 

相信聪明的你已经看出来了, 我们可以在拦截函数中为所欲为😏

除了用实例化(new)的方法进行代理对象的创建, 还可以创建一个可撤销的proxy对象, 看码 :

const bar = {
  key: 1
}
let { proxy,revoke } = Proxy.revocable(bar,handler)
console.log(proxy.key)
// > get
// > 1

// 此时调用revoke可以撤销代理
revoke() //撤销代理,后续不能再访问代理对象的属性
console.log(proxy.key) 
// Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked 

对被代理对象的操作拦截后, 必须手动加入拦截完的处理方法(或默认行为), 否则会影响预期输出

上文代码中也有用到 Object 对应的方法来进行默认行为的操作, 但既有Object为何还要有Reflect?下文详细介绍

Reflect(ES6 为了操作对象而提供的新 API)

Why Reflect? 读完MDN的介绍后其实还是一头雾水

image.png

为什么要跟 proxy handler 的命名相同? 为什么又有一些方法与 Object 相同又有一些细微差别???

先解析: *为什么要跟Proxy handler的命名相同? *

在进行对象行为拦截的时候, 当我们不想修改默认行为的时候, 往往需要重复的写一些对象的默认行为的代码, 比如上文代码中有些使用 Object 上已有的方法, 而 Object 没有的方法就用其他方式代替, 而Reflect恰恰有_proxy handlers_ 的所有方法, 那么只要在每一个拦截的方法里面调一个跟handler同名的Reflect的静态方法就可以啦!

比如:

const handler = {
    ownKeys(target) {
        return Reflect.ownKeys(target)
    },
    apply(target, thisArg, argumentsList) {
        return Reflect.apply(...arguments)
    }
    //...
} 

是不是很棒?

再解释: 为什么又有一些方法与_Object_相同又有一些细微差别???

所谓的细微差别, 不就是大体相同, 小部分不同么?

之所以要有Reflect, 一是为了使一些对象操作的返回值更加合理一些, 比如:

Object.defineProperty(obj, name, desc) 在无法定义属性时,会抛出一个错误,定义成功时返回修改后的对象。

Reflect.defineProperty(obj, name, desc) 在定义属性成功时返回 true ,失败时返回 false, 明显更加容易理解, 也更符合常理

二儿一个:

有些 Object 的操作是命令式的, 将其统一转换成函数式, 比如 property in objectdelete object[property] ,而 Reflect.has(object, property)Reflect.deleteProperty(object, property) 让它们变成了函数行为

Vue3用proxy有啥好处?

相信背过面试题目的同志都知道, vue2 的响应式是用了 Object.definePropertydata 对象进行递归遍历, 把它的所有属性都转换为 gettersetter 并在内部追踪其依赖, 当其依赖属性更新时, 去更新界面…

那么vue3流行以后的面试题就变成了大概如 "Vue3用proxy有啥好处? 跟vue2有什么不一样? "

要说Proxy的好处,得先说说 Object.defineProperty 的不足

熟练运用vue2的同志们都知道, 某些情况下我们修改data的时候并不具响应性, 比如:

  • 添加一个原本没有的属性
  • 通过下标修改数组元素值

因为 Object.defineProperty 实例初始化的时候就调用了, 此时只会把原来已有的属性进行监听, 新加的属性没法监听, 虽说 vue2 也有它的解决方案, 就是 Vue.$set, 但毕竟不够灵活和高效

上文已经介绍过, Proxy的作用是可以对对象的所有操作进行监听, vue3使用proxy便可保证data的所有更新操作都能被正确地监听到, 此处举一个简化版的例子:

// proxy只能监控一层的数据变化,深层次属性读取无法调用set,因此vue3中进行了优化
// 递归读取对象,为每个类型为对象的子属性都创建一个proxy
// 读取子属性时也会触发上一层的get
let bar = {user:{hobbit:'play',food:{fruit: 'apple'}}}
const isObject = (val)=>{
    return val !== null && typeof val === 'object'
}
const createProxy = (target)=>{
    let p = new Proxy(target,{
        get:(target, property)=>{
            console.log('get')
            let res = target[property] ? target[property] : undefined
            // 判断类型,避免死循环
            if (isObject(res)) {
                return createProxy(res)
            } else {
                return Reflect.get(...arguments)
            }
        },
        set: (target, property, value)=> {
          console.log('set')
          return Reflect.set(target, property, value)
        }
    })
    return p
}

let result = createProxy(bar)

result.user.food.fruit = 'pear'
result.user.age = 12
console.log(result)
// { user: { hobbit: 'play', food: { fruit: 'pear' }, age: 12 } } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值