ES6(ECMAScript2015) 是
JavaScript
一个版本标准,作为前端爱好者是必须要掌握的,本篇记录我对ES6中,proxy
的理解。
字面理解
Proxy
在英语中是代理的意思,那我们就引发了几个疑问:
- 它代理了什么?
- 为什么要代理?
- 如何使用代理?
首先我们引用阮老师的 《ECMAScript 6 入门》
书籍中的概述:
Proxy 可以理解成,在目标对象之前架设一层“拦截器”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
那么通过阮老师的这句话,我们可以得出第一个问题的答案:
它代理了对象
为什么要代理
第二个问题是为什么要使用 proxy
?我总结了以下几点:
- 拦截和监视外部对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
先来一个不使用 proxy
的栗子:
let user = {
name: 'John',
surname: 'Doe'
};
let printUser = (property) => {
let value = user[property];
if (!value) {
throw new Error(`The property [${property}] does not exist`);
} else {
console.log(`The user ${property} is ${value}`);
}
}
printUser('name'); // 输出: 'The user name is John'
printUser('email'); // 抛出错误: The property [email] does not exist
这是一个简单的例子,我们有一个带有几个属性的用户对象,如果属性存在,我们想要打印用户信息,如果不存在,则抛出异常。通过上面的代码,你会发现:将条件和异常移到其他地方,而 printUser
中仅关注显示用户信息的实际逻辑会更好。这是我们可以使用代理对象的地方,让我们更新下这个例子。
let user = {
name: 'John',
surname: 'Doe'
};
let proxy = new Proxy(user, {
get(target, property) {
let value = target[property];
if (!value) {
throw new Error(`The property [${property}] does not exist`);
}
return value;
}
});
let printUser = (property) => {
console.log(`The user ${property} is ${proxy[property]}`);
};
printUser('name'); // 输出: 'The user name is John'
printUser('email'); // 抛出错误: The property [email] does not exist
在上面的示例中,我们包装了 user
对象,并设置了一个 get
方法。此方法充当拦截器,在返回值之前,会首先对属性值进行检查,如果不存在,则抛出异常。
输出与第一种情况相同,但此时 printUser
函数专注于逻辑,只处理消息。
实例方法
proxy除了代理get,set操作,还能代理其他的操作,如下:
handler.getPrototypeOf()
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.setPrototypeOf()
// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.isExtensible()
// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.preventExtensions()
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.getOwnPropertyDescriptor()
// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.defineProperty()
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
handler.has()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.get()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.set()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.deleteProperty()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.ownKeys()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.apply()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.construct()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
proxy栗子
通过属性查找数组中的特定对象
以下代理为数组扩展了一些实用工具,可一看到,你可以灵活地“定义”属性,而不需要使用 Object.defineProperties
方法。
let products = new Proxy([
{ name: 'Firefox', type: 'browser' },
{ name: 'SeaMonkey', type: 'browser' },
{ name: 'Thunderbird', type: 'mailer' }
],
{
get: function(obj, prop) {
// 缺省行为是返回属性值, prop ?通常是一个整数
if (prop in obj) {
return obj[prop];
}
// 获取 products 的 number; 它是 products.length 的别名
if (prop === 'number') {
return obj.length;
}
let result, types = {};
for (let product of obj) {
if (product.name === prop) {
result = product;
}
if (types[product.type]) {
types[product.type].push(product);
} else {
types[product.type] = [product];
}
}
// 通过 name 获取 product
if (result) {
return result;
}
// 通过 type 获取 products
if (prop in types) {
return types[prop];
}
// 获取 product type
if (prop === 'types') {
return Object.keys(types);
}
return undefined;
}
});
console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3