Proxy 用于修改某些操作的默认行为,等于是在语言层面做出了修改,也就是对编程语言进行改动。具体来说,Proxy就是一种机制,用来拦截外界对目标对象的访问,可以对这些访问进行过滤或者改写,所以Proxy更像是目标对象的代理器。
1、ES6 原生提供Proxy构造函数,可以用来生成proxy实例:
(1)实例
let proxy = new Proxy(target, handler);
接收两个参数:
target 是要代理的目标对象;
handler 也是一个对象,用来定义拦截的具体行为;如果拦截具有多个操作,就可以这样定义handler {fn, ….}
(2)Proxy 拦截操作汇总,共13个:
get(target, propKey, receiver)
:拦截对象属性的读取,比如proxy.foo
;set(target, propKey, value, receiver)
:拦截对象属性的设置,比如proxy.foo = v
,返回一个布尔值;has(target, propKey)
:拦截propKey in proxy
的操作,返回一个布尔值;deleteProperty(target, propKey)
:拦截delete proxy[propKey]
的操作,返回一个布尔值;ownKeys(target)
:拦截
Object.getOwnPropertyNames(proxy)
、
Object.getOwnPropertySymbols(proxy)
、
Object.keys(proxy)
、
for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey)
: 拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。defineProperty(target, propKey, propDesc)
:拦截
Object.defineProperty(proxy, propKey, propDesc)
、
Object.defineProperties(proxy, propDescs)
,返回一个布尔值。preventExtensions(target)
:拦截Object.preventExtensions(proxy)
,返回一个布尔值。getPrototypeOf(target)
:拦截Object.getPrototypeOf(proxy)
,返回一个对象。isExtensible(target)
:拦截Object.isExtensible(proxy)
,返回一个布尔值。setPrototypeOf(target, proto)
:拦截Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。apply(target, object, args)
:拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)
。construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
。
(3)Proxy常用拦截方法详解(暂时就用到这两个,过后再补):
get ( ): 如果某个属性不可写或者不可配置,则该属性不能被访问,set()也同理;
如上所述,这个get() 方法用来拦截对目标对象的属性访问;还是上实例吧:
let idiot= {
name: "Troyes",
age: 39
};
let proxy = new Proxy(idiot, {
get (target, property) {
//我天,有个return怎么就用不了三元操作符?
if (property in target){
return target[property];
}else{
throw new ReferenceError("the property you want to get:"+ property +" does not exist!");
}
//老衲有妙招:就是有点烦,flag无论真假,都是return出去,容我以后再来想想能不能优化
//return (property in target) ? (target[property]) : ( "the property you want to get:"+ property +" does not exist!");
}
});
console.log(proxy.name, proxy.age); //Troyes 39
console.log(proxy.job); //ReferenceError: the property you want to get:job does not exist!
set ( ):
这个方法用于拦截对某个属性的赋值操作,废话不都说,上实例:
let checker = {
set (target, prop, val) {
//验证设置的特定的属性值符不符合要求
if (prop=== 'age') {
if (Number.isInteger(val)) {
if (val>300) {
throw new RangeError("Are you f**king kiding me?");
}
}else{
throw new TypeError("The age is not a integer.");
}
}
//无要求的属性值,符合要求的age都直接存起来
target[prop] = val;
}
};
let Person = new Proxy({}, checker);
Person.age = 35;
console.log(Person.age); //35
Person.age = "123d"; //TypeError: The age is not a integer.
Person.age = 1000; //RangeError: Are you f**king kiding me?
Person.name = "troyes";
console.log(Person.name); //troyes
(4)关于 This
用了 Proxy
之后,proxy代理的 this 并非指向目标对象,而是指向自身proxy。所以这里就会有坑:某些原生对象的 this 指向如果不对,是无法访问到他的内部属性的;(解决方法下面有讲到)
let a = {
toConsole () {
if (this === proxy) {
console.log("the 'this' of a change its pointing to proxy.");
}else if (this === a) {
console.log("waiting to chang its pointing.");
}
}
};
let proxy = null;
a.toConsole(); //waiting to chang its pointing.
proxy = new Proxy( a, {});
a.toConsole(); //waiting to chang its pointing. 在a中,a的this没变,还是a;
proxy.toConsole (); //the 'this' of a change its pointing to proxy. 但是在proxy中,this却已经指向了proxy;
有些原生对象的内部属性的this不对就会报错;那么知道坑在这里,怎么解决呢?
答案就是通过绑定this,写 handler 这个参数的时候,通过 bind(target) 直接把this 绑定到原生对象 target上 ;
let target = new Date();
let handler = {
get (target, prop) {
if (prop === 'getTime') {
//直接把this绑定到target上
return target.getTime.bind(target);
}
return Reflect.get(target, prop);
}
};
let proxy = new Proxy(target, handler);
let nowTime = proxy.getTime();
console.log(nowTime); //1536074540607
//加上这句,直接报错
proxy.getDay(); //TypeError: this is not a Date object.
写到这里,索性再来说说 bind(obj)
这个方法吧;
正如上面说的,调用这个方法就会直接把 this 直接绑定到 obj 上;
var module = {
x: 42,
getX: function () {
return this.x;
}
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope, the this is pointing to window, so window.x is undefined
// expected output: undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
//bind the this to module
// expected output: 42