Proxy是ES6中提供的新的API,可以用来定义对象各种基本操作的自定义行为 (在文档中被称为traps,我觉得可以理解为一个针对对象各种行为的钩子),拿它可以做很多有意思的事情,在我们需要对一些对象的行为进行控制时可以使用 Reflect 将变得非常有效。
定义
Proxy 和 Reflect 都属于 JavaScript 标准内置对象 的 反射 分类,不同的是:
Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。
术语
包含捕捉器(trap)的占位符对象,可译为处理器对象。
- traps(各种行为的代理)
提供属性访问的方法。这类似于操作系统中捕获器的概念。
- target
被 Proxy 代理虚拟化的对象。它常被作为代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。
注:
被代理的对象, 都是浅拷贝(及传址)。
语法
const p = new Proxy(target, handler)
参数:
target
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
适用场景
- 代理getter
const handler = {
get(obj, prop) {
return prop in obj ? Reflect.get(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
通过 反射对象 来获取属性,如果没有则返回 37
- 无操作转发代理
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作转发到目标
console.log(target.a); // 37. 操作已经被正确地转发
- 验证
通过代理,你可以轻松地验证向一个对象的传值。下面的代码借此展示了 set
handler 的作用。
let validator = {
set(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');
}
}
// 利用反射类来设置属性
Reflect.set(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
- 扩展构造函数
方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了construct
和apply
。
function extend(sup, base) {
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype, "constructor"
);
base.prototype = Object.create(sup.prototype);
var handler = {
construct(target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply(target, that, args) {
sup.apply(that, args);
base.apply(that, args);
}
};
var proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}
var Person = function (name) {
this.name = name
};
var Boy = extend(Person, function (name, age) {
this.age = age;
});
Boy.prototype.sex = "M";
var Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13
解决对象属性为undefined的问题
在实际应用中,我们调用对象的多级属性时,当属性的父级不存在时经常会报 VM241:2 Uncaught TypeError: Cannot read property 'c' of undefined
的错误,如下:
那么,我们可以使用 递归的Proxy
来代理 traps 的 get 方法, 来解决这个问题:
let handlers = {
get (target, property) {
if (Reflect.has(target, property)) {
return Reflect.get(target, property);
} else {
if (typeof target === 'undefined') {
target = {};
}
if (typeof target[property] === 'undefined') {
target[property] = {};
}
return new Proxy(target[property], handlers);
}
}
}
let a = {};
let proxy = new Proxy(a, handlers)
console.log(proxy.a.b.c);
结果如下:
可以看到,并未报错,而是返回了一个 Proxy 的实例对象。