代理模式
使用 Proxy 对象,我们可以更好地控制与某些对象的交互。代理对象可以在与对象交互时决定一些行为,例如当我们获取或设置对象的值时。
一般来讲,代理表示做他人的替身。你将与代表你尝试联系的人的代理人联系,而不是直接与该人联系。在 JavaScript 中也是如此:我们将与 Proxy 对象交互,而非与目标对象交互。
我们创建一个代表 John Doe 的 person
对象。
const person = {
name: "John Doe",
age: 42,
nationality: "American"
};
我们希望与代理对象进行交互,而不是直接与此对象交互。在 JavaScript 中,我们可以通过创建一个新的 Proxy
实例来轻松创建代理。
const person = {
name: "John Doe",
age: 42,
nationality: "American"
};
const personProxy = new Proxy(person, {});
Proxy 的第二个参数是一个代表 处理器对象 (handler)。在处理程序对象中,我们可以根据交互类型定义具体的行为。尽管有许多方法能被添加到代理处理器对象中,但最常见的两个是 get
和 set
:
get
: 尝试 访问 属性时调用set
: 尝试 修改 属性时调用
实际上,最终会发生以下情况:
我们将与 personProxy
进行交互,而不是直接与 person
对象交互。
让我们向 personProxy
代理中添加处理器。当尝试修改属性时,调用 Proxy
上的 set
方法时,我们希望代理打印该属性的旧值和新值。当试图访问一个属性,从而调用代理上的 get
方法时,我们希望这个代理能打印更易读的内容,其中包含该属性的键和值。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${obj[prop]}`);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
obj[prop] = value;
}
});
完美!让我们看看当我们尝试修改或检索属性时会发生什么。
const person = {
name: "John Doe",
age: 42,
nationality: "American"
};
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${obj[prop]}`);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
obj[prop] = value;
return true;
}
});
personProxy.name;
personProxy.age = 43;
当访问 name
属性时,Proxy 返回了一个更好听的句子: The value of name is John Doe
。
当修改 age
属性时,Proxy 返回了此属性的旧值和新值: Changed age from 42 to 43
。
代理的一个用途在于可添加 验证 。用户不应该能够将 person
的年龄更改为字符串值,或者给他们一个空名字。或者如果用户试图访问对象上不存在的属性,我们应该让用户知道。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
if (!obj[prop]) {
console.log(
`Hmm.. this property doesn't seem to exist on the target object`
);
} else {
console.log(`The value of ${prop} is ${obj[prop]}`);
}
},
set: (obj, prop, value) => {
if (prop === "age" && typeof value !== "number") {
console.log(`Sorry, you can only pass numeric values for age.`);
} else if (prop === "name" && value.length < 2) {
console.log(`You need to provide a valid name.`);
} else {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
obj[prop] = value;
}
}
});
让我们看看当我们试图传递错误的值时会发生什么!
const person = {
name: "John Doe",
age: 42,
nationality: "American"
};
const personProxy = new Proxy(person, {
get: (obj, prop) => {
if (!obj[prop]) {
console.log(`Hmm.. this property doesn't seem to exist`);
} else {
console.log(`The value of ${prop} is ${obj[prop]}`);
}
},
set: (obj, prop, value) => {
if (prop === "age" && typeof value !== "number") {
console.log(`Sorry, you can only pass numeric values for age.`);
} else if (prop === "name" && value.length < 2) {
console.log(`You need to provide a valid name.`);
} else {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
obj[prop] = value;
}
return true;
}
});
personProxy.nonExistentProperty;
personProxy.age = "44";
personProxy.name = "";
代理确保我们没有用错误的值修改 person
对象,这有助于我们保持数据的纯净!
反射
JavaScript 提供了一个名为 Reflect
的内置对象,它能让我们在使用代理时更容易操作目标对象。
以前,我们尝试通过使用方括号表示法直接获取或设置值来修改和访问代理中目标对象的属性。现在,我们可以使用 Reflect
对象。Reflect
对象上的方法与 handler
对象上的方法同名。
我们可以通过 Reflect.get()
和 Reflect.set()
访问或修改目标对象上的属性,而不是通过 obj[prop]
访问属性或通过 obj[prop] = value
设置属性。这些方法接收的参数与处理器对象上的方法相同。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
Reflect.set(obj, prop, value);
}
});
完美!我们可以使用 Reflect
对象轻松访问和修改目标对象的属性。
const person = {
name: "John Doe",
age: 42,
nationality: "American"
};
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
return Reflect.set(obj, prop, value);
}
});
personProxy.name;
personProxy.age = 43;
personProxy.name = "Jane Doe";
代理是添加对对象行为的控制的强大方法。代理可以有各种使用方法:它可以验证、格式化、通知或调试。
过度使用 Proxy
对象或对每个 handler
方法调用执行繁重的操作很容易对应用程序的性能产生负面影响。最好不要将代理用于性能优先的代码。
参考
- Proxy - MDN
- JavaScript Proxy - David Walsh
- Awesome ES2015 Proxy - GitHub @mikaelbr
- Thoughts on ES6 Proxies Performance - Valeri Karpov