前几天模拟实现了 MobX 的两个函数 —— 手写实现 MobX 的 observable 和 autorun 方法,其中用到了 Proxy,所以打算再对 Proxy 深入了解一下,做个笔记。
Proxy 是什么
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
const p = new Proxy(target, handler)
- target: 使用 Proxy 包装的目标对象(可以是任何类型的 JavaScript 对象,包括原生数组,函数,甚至另一个代理)。
 - handler: 一个通常以函数作为属性的对象,用来定制拦截行为。
 
在支持 Proxy 的浏览器环境中,Proxy 是一个全局对象,可以直接使用。Proxy(target, handler)是一个构造函数,target是被代理的对象,最终返回一个代理对象。
为什么需要 Proxy
学习一样东西之前我们先要想想为什么需要它,在我看来,一般几种情况。
- 被代理的对象不想直接被访问
 - 控制和修改被代理对象的行为(调用属性、属性赋值、方法调用等等),使之可以进行访问控制和增加功能。
 
API
API 概览如下:
- get(target, propKey, receiver):拦截对象属性的读取,比如 proxy.foo 和 proxy[‘foo’] 。
 - set(target, propKey, value, receiver):拦截对象属性的设置,比如 proxy.foo = v 或 proxy[‘foo’] = v ,返回一个布尔值。
 - has(target, propKey):拦截 propKey in proxy 的操作,返回一个布尔值。
 - deleteProperty(target, propKey):拦截 delete proxy[propKey]的操作,返回一个布尔值。
 - ownKeys(target):拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、 Object.keys(proxy) 、 for…in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
 - getOwnPropertyDescriptor(target, propKey):拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。
 - defineProperty(target, propKey, propDesc):拦截 Object.defineProperty(proxy, propKey, propDesc) 、
 - Object.defineProperties(proxy, propDescs),返回一个布尔值。
 - preventExtensions(target):拦截 Object.preventExtensions(proxy) ,返回一个布尔值。
 - getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy),返回一个对象 。
 - isExtensible(target):拦截 Object.isExtensible(proxy) ,返回一个布尔值。
 - setPrototypeOf(target, proto):拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args)、proxy.apply(…)`。
 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args) 。
 
最常用的方法就是get和set
例子
get
1  | const target = {  | 
运行,打印台输出:
1  | 获取名字/性别  | 
在获取name属性是先进入get方法,在get方法里面打印了获取名字/性别,然后通过Reflect.get(target, key)的返回值拿到属性值,相当于target[key]
set
1  | const target = {  | 
运行输出:
1  | 获取名字/性别  | 
设置proxy.name = 'monkey',这是修改属性的值,则会触发到set方法, 然后我们强行更改设置的值再返回,达到拦截对象属性的设置的目的。
1  | Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。  | 
this 指向
proxy 会改变 target 中的 this 指向,一旦 Proxy 代理了 target,target 内部的 this 则指向了 Proxy 代理
1  | const target = new Date('2021-01-03')  | 
运行代码,会发现报错,提示TypeError: proxy.getDate is not a function,即 this 不是 Date 对象的实例,这时需要我们手动绑定原始对象即可解决:
1  | const target = new Date('2021-01-03')  | 
应用场景
- 警告或阻止特定操作
 - get 方法取不到对应值可以返回我们想指定的其它值
 - 数据校验。判断数据是否满足条件
 
等等…
参考
觉得不错的话赏个 star,给我持续创作的动力吧!