这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战
Proxy 简介
Proxy 翻译过来叫做代理,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
基本语法
const p = new Proxy(target, handler)
target要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理proxy的行为,如果没有定义,则会保留源对象的默认行为。
简单实例
我们先看一个空代理,也就是不定义 handler 进行额外操作,代理对象执行的所有操作都会传播到目标对象,同样目标对象任何改变也会传到代理对象,我们看下面实例。
const target = { id: "target" };
const handler = {};
const targetProxy = new Proxy(target, handler);
//1. id属性会访问同一个值
console.log(target.id, targetProxy.id); // target target
//2. 给目标属性赋值会反映在两个对象上, 因为两个对象访问的是同一个值
target.id = "foo";
console.log(target.id, targetProxy.id); // foo foo
//3. 给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象
targetProxy.id = "bar";
console.log(target.id, targetProxy.id); // bar bar
//4. hasOwnProperty()方法在两个地方 都会应用到目标对象
console.log(target.hasOwnProperty("id"), targetProxy.hasOwnProperty("id")); // true true
//5. Proxy.prototype是undefined
// 因此不能使用instanceof操作符
//instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链
// TypeError: Function has non-object prototype
console.log(target instanceof Proxy,proxy instanceof Proxy);
'undefined' in instanceof check
//6. 严格相等可以用来区分代理和目标
console.log(target === proxy); // false
小结:
我们可以发现,目标对象,和代理他们访问的值相同,任何一方做出改变,另外一方也会做出同样改变,他们就像镜像一样,拥有相同的内容和行为。
Proxy要点详解
我们现在大致知道了何为代理,以及如何创建一个基本的代理,那么新的疑问出现了,我们为什么要使用代理,使用代理可以给我们带来什么,我们接着往下看。
使用代理的目的
可以定义捕获器(trap),捕获器可以直接或者间接的在代理对象上调用,每次在代理对象上调用这些基本操作的时候,代理可以在这些操作传播到目标对象之前先调用捕获函数,从而拦截,并且做出自己定义的行为。
通俗的说就是,捕获器在被代理的对象被调用前先执行,从而拦截并且做出相应的行为,类似加了一层 if 判断,或者说是弹框确认。
我们来看个例图 :
小结:我们可以看到,一个对象经过代理后,代理身上的操作会相应的执行到对象本身,在代理中有一个 handler 捕获器,可以捕获到操作类型。 例如当用户操作时候,会经过 handler 里的捕获器, 如果用户的操作符合捕获类型,就会进入该捕获器,然后再捕获器中可以规范用户的行为。
如何定义捕获器
在处理程序对象 handler 中,我们可以定义一个或者多个捕获器(trap)去处理代理触发的行为,如果没有定义,则使用默认行为。
一些常用的捕获器
首先我们来看一下最常用的 handle 对象的方法
handler.get()属性读取操作的捕捉器。
属性读取操作的捕捉器。
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
小结:我们可以看到,我们在 handler 中定义了一个属性读取捕获器,当代理对象收到属性读取操作的时候,会进入该捕获器中,我们看到,当我们读取 p代理时,进入了捕获器 get ,然后发现里面没有 c 属性,然后返回了一个37。
handler.set()属性设置操作的捕捉器。
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// The default behavior to store the value
obj[prop] = value;
// 表示成功
return true;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age);
// 100
person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer
person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid
小结:我们可以看到我们定义了一个属性设置捕获器 ,当对象属性被设置时会进入该代理,我们可以看到他做了几层限制,不是 age 属性不要,不是数字类型报错,值大于 200 也报错,然后我们看到相应结果果然如此。
handler.deleteProperty() delete操作符的捕捉器。
var p = new Proxy({}, {
deleteProperty: function(target, prop) {
console.log('called: ' + prop);
return true;
}
});
delete p.a; // "called: a"
小结:我们可以看到,当 Proxy 对象 p 的属性 a 被删除的时候,会进入该捕获器
其余捕获器概述(点击跳转至文档详情)
Object.getPrototypeOf 方法的捕捉器。
Object.setPrototypeOf 方法的捕捉器。
Object.isExtensible 方法的捕捉器
Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor 方法的捕捉器。
Object.defineProperty 方法的捕捉器。
handler.has()in 操作符的捕捉器。
handler.get()属性读取操作的捕捉器。
handler.set()属性设置操作的捕捉器。
handler.deleteProperty()delete 操作符的捕捉器。
handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply()函数调用操作的捕捉器。
handler.construct()new 操作符的捕捉器。
一些不标准的捕捉器已经被废弃并且移除了。