十四、Proxy
Proxy 实例的方法
get()
set()
apply()
has()
construct()
deleteProperty()
defineProperty()
getOwnPropertyDescriptor()
getPrototypeOf()
isExtensible()
ownKeys()
preventExtensions()
setPrototypeOf()
注意:
handler可以写在函数里面,如果需要复用或者需要创建proxy时简便一些可以单独声明。第一个参数也是如此。
var handler = {
get:function(){},
set:function(){}
}
var obj = {}
var proxy1 = new Proxy(obj,handler)
var proxy2 = new Proxy(obj,{
get:function(){},
set:function(){}
})
1、get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
var person = {
name: "周子越"
};
var proxy = new Proxy(person, {
get: function(target, propKey, p) {
console.log('目标对象->',target) // 目标对象->,{"name":"周子越"}
console.log('键名->',propKey) // 键名->,name
return target[propKey]
}
});
proxy.name // 打印的话就是 周子越
/**
* 目标对象->,{"name":"周子越"}
* 键名->,name
*/
proxy.age // 打印的话就是undefined
/**
* 目标对象->,{"name":"周子越"}
* 键名->,age // 虽然没有但是还是打出了传入的键名
*/
var childProxy = Object.create(proxy) // 以proxy为原型创建一个对象并返回
childProxy.sex
/**
* 目标对象->,{"name":"周子越"}
* 键名->,sex
* 可见get被继承了
*/
2、set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
var person = {
name: "周子越"
};
var proxy = new Proxy(person, {
get: function(target, propKey, p) {
console.log('目标对象->',target) // 目标对象->,{"name":"周子越"}
console.log('键名->',propKey) // 键名->,name
return target[propKey]
},
set: function(target, propKey, value, p){
target[propKey] = value
console.log(`${propKey}对应的值变为${value}`)
return true // 注意如果没有返回true 在严格模式下会报错
}
});
proxy.name = '123' // name对应的值变为123
proxy.name
/**
* 目标对象->,{"name":"123"}
* 键名->,name
*/
proxy.sex = '男' // sex对应的值变为男
proxy.sex
/**
* 目标对象->,{"name":"123","sex":"男"}
* 键名->,sex
*/
3、apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var target = function(){
return 'I am the target'
}
var handler = {
apply:function(){
return 'I am the hanlder'
}
}
var proxy = new Proxy(target,handler)
console.log(proxy()) // I am the handler
console.log(proxy.call())// I am the handler
console.log(proxy.apply())// I am the handler
尽管proxy作为一个object而非function,但当他作为函数调用、call调用、apply调用时,被proxy拦截,
4、has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has方法可以接受两个参数,分别是目标对象、需查询的属性名。
下面是隐藏私有变量的两种handler的实现
var handler1 = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var handler2 = {
has (target, key) {
if (key.search(/^_/)>-1) {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler2);
console.log('_prop' in proxy) // false
注意has的拦截对for in不生效
5、construct()方法用于拦截new命令,下面是拦截对象的写法。
construct方法可以接受三个参数。
target:目标对象
args:构造函数的参数对象
newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p)
var handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};
6、deleteProperty()方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
拦截删除私有变量的操作。
var handler = {
deleteProperty(target, key) {
if (key.search(/^_/) > -1) {
console.log('不可删除私有变量')
return false
}
delete target[key];
console.log('已经删除变量'+key)
return true;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop //不可删除私有变量
7、defineProperty()
defineProperty()方法拦截了Object.defineProperty()操作。
var handler = {
defineProperty(target, key, descriptor) {
return false;
}
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'// 不会生效
console.log(proxy.foo) // undefined
因为每次都返回false,所以任何对属性的定义都无效。
8、getOwnPropertyDescriptor()
getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
拦截所有企图获取私有变量描述对象的操作
var handler = {
getOwnPropertyDescriptor(target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }
9、getPrototypeOf()
getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。只接受一个target对象
Object.prototype._ _ proto _ _
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
var proto ={};
var p =newProxy({},{
getPrototypeOf(target){
return proto;
}
});
Object.getPrototypeOf(p)=== proto // true
9、isExtensible()
isExtensible()方法拦截Object.isExtensible()操作。
10、ownKeys()
ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for…in循环
11、preventExtensions()
preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
12、setPrototypeOf()
setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。
Proxy.revocable()
Proxy.revocable()方法返回一个可取消的 Proxy 实例。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
this问题
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target。
下面是一个例子,由于this指向的变化,导致 Proxy 无法代理目标对象。
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
jane.name // 'Jane'
const proxy = new Proxy(jane, {});
proxy.name // undefined