温故而知新:JavaScript中proxy和reflect用法

221 阅读3分钟

前言

proxy和reflect都是ES6中新增的功能,其实在实际项目开发中单独使用的情况的不是特别多,vue3响应式核心就是基于它们实现的,所以有必要深入了解一下。

代理proxy

定义

官方给出的定义

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

简单来讲,Proxy用于给源对象增设一层代理对象,对代理对象的所有操作都会被捕捉器所捕获,并由捕获器决定是否将此次行为的操作映射到源对象上。需要注意的是只有对代理对象的操作才会被捕捉到,对源对象的操作不会被捕捉,使用proxy我们几乎可以捕获到对象的所有操作行为。

通过一张图来看看Proxy的结构

8179bf3d3b874619b5e27358b256a922_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

兼容性

Proxy是ES6中新增的标准,目前主流的浏览器都已支持,但是其他(比如IE)就不支持该特性,即便是babel也无法解决,因为Proxy是js底层新标准的实现,无法通过其他方式模拟兼容。

对于箭头函数,它也是ES6中新增的函数写法,但是babel可以将箭头函数转化为普通函数来进行兼容适配,而对Proxy却无能为力。

25fe8e7320f3413c9d03d462e767996b_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

使用

Proxy实际上是一个构造函数,通过new关键字直接调用生成代理对象,他接收两个参数,一个源对象,一个处理对象(包括各种处理方法), 缺少任何一个参数都会报错

const origin = {
    title: "hello world"
}
const handler = {
    get(origin, property) {},
    ...
}
const proxy1 = new Proxy(origin, handler) // 正确
const proxy2 = new Proxy(origin) // 报错

需要注意的是Proxy.prototypes是undefined,proxy.__proto__指向Object.prototype

Proxy中支持的拦截操作方法一共可以分为13类

const origin = {}
const proxy = new Proxy(origin, {
    get(origin, property){}, // 拦截对象属性的读取
    set(target, property){}, // 拦截对象属性的设置
    has(origin, property) {} // 拦截对象的property in proxy操作,返回一个布尔值
    deleteProperty(){} // 拦截delete proxy[property]操作,返回一个布尔值
    ownKeys(){} // 拦截Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(proxy), Object.keys(proxy), for...in循环,返回一个数组
    getOwnPropertyDescriptor(){} // 拦截Object.setOwnPropertyDescriptor(), 返回属性的描述对象
    defineProperty() {} // 拦截Object.defineProperty(),Object.defineProperties(),返回一个布尔值
    preventExtensions() {} // 拦截Object.preventExtensions() 返回一个布尔值
    getPrototypeOf(){} // 拦截Object.getPrototypeOf(),返回一个对象
    isExtensible(){} // 拦截Object.isExtensible() 返回一个布尔值
    setPrototypeOf(){} // 拦截Object.setPrototypeOf() 返回一个布尔值
    apply() {} // 拦截Proxy实例作为函数的操作,比如proxy(), proxy.call(), proxy.apply()
    constructor() {} // 拦截Proxy实例作为构造函数调用的操作,比如:new proxy()
})

reflect

在ES5中对对象进行操作的时候,通常是使用Object构造函数直接进行操作,而在ES6中使用的是标准的Reflect, 它的作用如下:

  • 将Object对象的一些明显属于语言内部的方法,放到Reflect对象上,比如:Object.defineProperty()
  • 修改某些Object方法返回的结果,让其变得更合理,比如:Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回fasle,更加合理
  • 让Object操作都变成函数行为,某些Object操作是命令式的,比如:name in Obj 和 delete Obj[name], 而Reflect.has(obj.name) 和 Reflect.deleteProperty(obj.nama)让他们变成函数式,更加直观
  • Reflect对象的方法与Proxy代理对象的方法是一一对应的,只要是在Proxy上能找到的方法,就能在Reflect对象上找到对应的方法