JavaScript 中 Proxy 的理解
介绍
Proxy 是由 ES6 提供的一种机制,可以对外界的访问进行过滤和改写;可以理解成在目标对象之前架设一层 "拦截", 外界对这个对象的访问,都必须先通过这层拦截;
Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”;
用法
Proxy
为构造函数,用来生成 Proxy
实例
var proxy = new Proxy(target, handler);
上面的代码,new Proxy()
生成一个 Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来指定拦截行为;
一个简单的示例:
const person = {
name: "赵云",
age: 21
}
const proxy = new Proxy(person, {
get: function(target, propKey) {
console.log(target);
console.log(propKey);
return target[propKey];
}
});
// {name: '赵云', age: 21}
// name
// 赵云
console.log(proxy.name);
上面的代码,创建了一个 person 对象,它有两个属性,分别为 name 和 age; 实例化了一个 Proxy ,将 person 作为第一个参数(所要拦截的目标对象),第二个传入的参数是一个对象,它有一个 get 属性,值是一个回调函数;
Proxy 支持的拦截操作
实例化 Proxy 的第二个参数是一个对象,其中可以指定 Proxy 支持的拦截操作;对几种常见的拦截操作进行举例说明;
- get(target, propKey, receiver)
get
方法用于拦截某个属性的读取操作;
get
方法接收三个参数,目标对象、属性名、proxy 实例本身(操作行为所针对的对象);最后一个参数是可选的;
示例:const person = { name: "赵云" } const proxy = new Proxy(person, { get: function(target, propKey) { if (propKey in target) { return target[propKey]; } else { throw new ReferenceError(`属性名${propKey}不存在`); } } }); console.log(proxy.name); // 赵云 console.log(proxy.age); // Uncaught ReferenceError: 属性名age不存在
- set(target,propKey,value,receiver)
set
方法用来拦截某个属性的赋值操作;
set
方法接收四个参数,目标对象、属性名、属性值和 Proxy 实例本身;最后一个参数是可选的;
示例:
假定 Person 对象有一个 age 属性,该属性应该是一个不大于 200 的整数,那么可以使用 Proxy 保证 age 的属性值符合要求;const person = { age: 20 } // 年龄大于 200 报错 const proxy = new Proxy(person, { set: function(target, propKey, propValue) { if (propKey === 'age') { if (!Number.isInteger(propValue)) { throw new ReferenceError(`age 不是一个整数`); }else if (propValue > 200) { throw new ReferenceError(`age 不能超过200`); } } // 满足条件的 age 及其他属性,直接保存 target[propKey] = propValue; return true; } }); proxy.age = 28; console.log(proxy.age); // 28 console.log(proxy.age = "28"); // Uncaught ReferenceError: age 不是一个整数 console.log(proxy.age = 288); // Uncaught ReferenceError: age 不能超过200
- has(target,propKey)
has()
方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效; 典型的操作就是in运算符;
has()
方法可以接受两个参数,分别是目标对象、需查询的属性名; - deleteProperty(target,propKey)
deleteProperty()
方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除; - ownKeys(target)
ownKeys()
方法用来拦截对象自身属性的读取操作; 具体来说,拦截以下操作;- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for...in循环
- getOwnPropertyDescriptor(target, propKey)
getOwnPropertyDescriptor()
方法拦截Object.getOwnPropertyDescriptor()
,返回一个属性描述对象或者 undefined; - defineProperty(target, propKey, propDesc)
defineProperty()
方法拦截了Object.defineProperty()
操作; - preventExtensions(target)
preventExtensions()
方法拦截Object.preventExtensions()
; 该方法必须返回一个布尔值,否则会被自动转为布尔值
注意:
这个方法有一个限制,只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false ),proxy.preventExtensions
才能返回 true,否则会报错; - getPrototypeOf(target)
getPrototypeOf()
方法主要用来拦截获取对象原型; 具体来说,拦截下面这些操作;- Object.prototype.proto
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
- isExtensible(target)
isExtensible()
方法拦截Object.isExtensible()
操作; - setPrototypeOf(target, proto)
setPrototypeOf()
方法主要用来拦截Object.setPrototypeOf()
方法; - apply(target, object, args)
apply
方法拦截函数的调用、call
和apply
操作;
apply
方法接受三个参数,目标对象、目标对象的上下文对象(this)、目标对象的参数数组; - construct(target, args)
construct()
方法用于拦截 new 命令,下面是拦截对象的写法
construct() 方法可以接收三个参数const proxy = new Proxy({}, { construct: function (target, args, newTarget) { return new target(...args); } });
1. target:目标对象;
2. args:构造函数的参数数组;
3. newTarget:创造实例对象时,new命令作用的构造函数
取消代理
Proxy.revocable(target, handler);
使用场景
- 数据校验(校验表单)
let data = { count: 8 }; data = new Proxy(data, { set: function(target, propKey, propValue, proxy) { if (propKey === "count" && typeof propValue !== 'number') { // 不是 number 类型抛出错误 throw Error(`count 属性只能设置 number 类型数据`); } return Reflect.set(target, propKey, propValue, proxy); } }); data.count = 88; console.log(data.count); // 88 console.log(data.count = "99"); // Uncaught Error: count 属性只能设置 number 类型数据
- 私有化 api, 防止内部属性被外部读写
let api = { _apiKey: "123abc456def", getUser: function(userId) {}, setUser: function(userId, config) {} } console.log(api._apiKey); // 此处可用 const restricted = ['_apiKey']; api = new Proxy(api, { set: function(target, propKey, propsValue, proxy) { if (restricted.indexOf(propKey) > -1) { throw Error(`${propKey} 不可访问`); } return Reflect.set(target, propKey, propsValue, proxy); }, get: function(target, propKey, proxy) { if (restricted.indexOf(propKey) > -1) { throw Error(`${propKey} 不可访问`); } return Reflect.get(target, propKey, proxy); } }); // 一下操作都会抛出错误 api._apiKey; api._apiKey = "123";
- 预警,拦截,过滤
- 实现观察者模式
- 服务端代理,跨域,取消请求等