Proxy 拦截器
Proxy,见名知意,其功能非常类似于设计模式中的代理模式,该模式常用于三个方面:
拦截和监视外部对对象的访问
降低函数或类的复杂度
在复杂操作前对操作进行校验或对所需资源进行管理
在支持 Proxy 的浏览器环境中,Proxy 是一个全局对象,可以直接使用。Proxy(target, handler) 是一个构造函数,target 是被代理的对象,handlder 是声明了各类代理操作的对象,最终返回一个代理对象。外界每次通过代理对象访问 target 对象的属性时,就会经过 handler 对象,从这个流程来看,代理对象很类似 middleware(中间件)。那么 Proxy 可以拦截什么操作呢?最常见的就是 get(读取)、set(修改)对象属性等操作。此外,Proxy 对象还提供了一个 revoke 方法,可以随时注销所有的代理操作。
const target = { name: 'li', age: 15 };
const handler = {
get(target, key, proxy) {
const today = new Date();
console.log(`GET request made for ${key} at ${today}`);
return Reflect.get(target, key, proxy);
}
};
const proxy = new Proxy(target, handler);
proxy.name;
// GET request made for name at Wed May 24 2017 14:36:37 GMT+0800 (中国标准时间)
//li
在上面的代码中,我们首先定义了一个被代理的目标对象 target,然后声明了包含所有代理操作的 handler 对象,接下来使用 Proxy(target, handler) 创建代理对象 proxy,此后所有使用 proxy 对 target 属性的访问都会经过 handler 的处理。
1、抽离验证类的代码
let numericDataStore = {
count: 0,
amount: 1234,
total: 14
};
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("Properties in numericDataStore can only be numbers");
}
return Reflect.set(target, key, value, proxy);
}
});
numericDataStore.count = "foo";// 抛出错误
numericDataStore.count = 333;// 赋值成功
2、在 JavaScript中实现真正的私有
var api = {
_apiKey: '123abc456def',
/* mock methods that use this._apiKey */
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
// Add other restricted properties to this array
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} is restricted. Please see api documentation for further info.`);
}
return Reflect.get(target, key, value, proxy);
}
});
// 抛出错误
console.log(api._apiKey);
// 抛出错误
api._apiKey = '987654321';
3、静默地对象访问日志
在不搞乱应用程序代码或者阻碍其执行的前提下对任何类型的东西进行日志记录
let api = {
_apiKey: '123abc456def',
getUsers: function() { /* ... */ },
getUser: function(userId) { /* ... */ },
setUser: function(userId, config) { /* ... */ }
};
api = new Proxy(api, {
get: function(target, key, proxy) {
var value = target[key];
return function(...arguments) {
logMethodAsync(new Date(), key);
return Reflect.apply(value, target, arguments);
};
}
});
// executes apply trap in the background
api.getUsers();
function logMethodAsync(timestamp, method) {
setTimeout(function() {
console.log(`${timestamp} - Logging ${method} request asynchronously.`);
}, 0)
}
4、提供警告信息或者阻止特定操作的执行
假设你想要阻止任何人删除属性 noDelete, 想要让那些调用 oldMethod 的用户知道它已经被弃用了, 还想要阻止任何人修改 doNotChange 属性。下面就是一种快速的实现办法。
let dataStore = {
noDelete: 1235,
oldMethod: function() {/*...*/ },
doNotChange: "tried and true"
};
const NODELETE = ['noDelete'];
const DEPRECATED = ['oldMethod'];
const NOCHANGE = ['doNotChange'];
dataStore = new Proxy(dataStore, {
set(target, key, value, proxy) {
if (NOCHANGE.includes(key)) {
throw Error(`Error! ${key} is immutable.`);
}
return Reflect.set(target, key, value, proxy);
},
deleteProperty(target, key) {
if (NODELETE.includes(key)) {
throw Error(`Error! ${key} cannot be deleted.`);
}
return Reflect.deleteProperty(target, key);
},
get(target, key, proxy) {
if (DEPRECATED.includes(key)) {
console.warn(`Warning! ${key} is deprecated.`);
}
var val = target[key];
return typeof val === 'function' ?
function(...args) {
Reflect.apply(target[key], target, args);
} :
val;
}
});
// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo";
delete dataStore.noDelete;
dataStore.oldMethod();
5、阻止非必要的重度资源消耗型操作
假设你有一个服务器端点会返回一个非常大的文件。你不想在之前的请求还在进行中,而文件也还在下载中,或者它已经被下载过来一次的时候再次发起请求。代理对此缓冲这种类型的访问以及尽可能获取缓存值方面是一个很好的架构, 而不是让用户去尝试尽可能频繁的发起对端点的调用。这里我将省略大多数代码,但下面所列出来的代码足够让你明白它是如何运作的了。
let obj = {
getGiantFile: function(fileId) {/*...*/ }
};
obj = new Proxy(obj, {
get(target, key, proxy) {
return function(...args) {
const id = args[0];
let isEnroute = checkEnroute(id);
let isDownloading = checkStatus(id);
let cached = getCached(id);
if (isEnroute || isDownloading) {
return false;
}
if (cached) {
return cached;
}
return Reflect.apply(target[key], target, args);
}
}
});
Reflect 反射
Reflect 是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与代理处理程序的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。Proxy相当于去修改设置对象的属性行为,而Reflect则是获取对象的这些行为。
Reflect和Proxy对象一样,也是ES6为了操作对象而提供的新的API:
(1)将Object对象一些明显属于语言层面的方法放在Reflect对象上,现阶段某些方法同时存在于Object和Reflect对象上,未来只会部署在Reflect对象上
(2)修改了某些Object方法返回值让他变得更加合理。如Object.defineProperty在无法定义属性时候抛出错误,但是Reflect.defineProperty返回false
(3)Object操作都变成函数行为。某些Object操作是命令式的,如name in obj和delete obj[name],而Reflect.has,Reflect.deletePropery变成函数行为
(4)Reflect方法和Proxy方法一一对应,只要是Proxy对象上存在那么就能在Reflect中找到,于是Proxy对象可以方便的调用对应的Reflect方法完成默认行为,作为修改行为的基础。也就是说,不管Proxy如何修改默认行为,你总是可以在Reflect上获取默认行为