Proxy是ES6中提供的新的API,可以用來定義對象各種基本操作的自定義行為 (在文檔中被稱為traps,我覺得可以理解為一個(gè)針對對象各種行為的鉤子),拿它可以做很多有意思的事情,在我們需要對一些對象的行為進(jìn)行控制時(shí)可以使用 Reflect 將變得非常有效。
定義
Proxy 和 Reflect 都屬于 JavaScript 標(biāo)準(zhǔn)內(nèi)置對象 的 反射 分類,不同的是:
Proxy 對象用于定義基本操作的自定義行為(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

Reflect 是一個(gè)內(nèi)置的對象,它提供攔截 JavaScript 操作的方法。這些方法與proxy handlers的方法相同。Reflect不是一個(gè)函數(shù)對象,因此它是不可構(gòu)造的。

術(shù)語
包含捕捉器(trap)的占位符對象,可譯為處理器對象。
- traps(各種行為的代理)
提供屬性訪問的方法。這類似于操作系統(tǒng)中捕獲器的概念。
- target
被 Proxy 代理虛擬化的對象。它常被作為代理的存儲后端。根據(jù)目標(biāo)驗(yàn)證關(guān)于對象不可擴(kuò)展性或不可配置屬性的不變量(保持不變的語義)。
注:
被代理的對象, 都是淺拷貝(及傳址)。
語法
const p = new Proxy(target, handler)
參數(shù):
target
要使用 Proxy 包裝的目標(biāo)對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)。handler
一個(gè)通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 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
- 無操作轉(zhuǎn)發(fā)代理
let target = {};
let p = new Proxy(target, {});
p.a = 37; // 操作轉(zhuǎn)發(fā)到目標(biāo)
console.log(target.a); // 37. 操作已經(jīng)被正確地轉(zhuǎn)發(fā)
- 驗(yàn)證
通過代理,你可以輕松地驗(yàn)證向一個(gè)對象的傳值。下面的代碼借此展示了 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');
}
}
// 利用反射類來設(shè)置屬性
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
- 擴(kuò)展構(gòu)造函數(shù)
方法代理可以輕松地通過一個(gè)新構(gòu)造函數(shù)來擴(kuò)展一個(gè)已有的構(gòu)造函數(shù)。這個(gè)例子使用了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的問題
在實(shí)際應(yīng)用中,我們調(diào)用對象的多級屬性時(shí),當(dāng)屬性的父級不存在時(shí)經(jīng)常會報(bào) VM241:2 Uncaught TypeError: Cannot read property 'c' of undefined 的錯(cuò)誤,如下:

那么,我們可以使用 遞歸的Proxy 來代理 traps 的 get 方法, 來解決這個(gè)問題:
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);
結(jié)果如下:

可以看到,并未報(bào)錯(cuò),而是返回了一個(gè) Proxy 的實(shí)例對象。