引入
系统学习ES6各种特性,了解背后的原理。
本章记录Proxy
笔记
1.1 Proxy概述
Proxy,用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种“元编程”。
Proxy可以理解成在目标对象前架设一个拦截层,外界对该对象的访问都要先通过该层,因此提供了一种机制可以对外界的访问进行过滤和改写。Proxy即代理,此处指的是由这个代理器来代理某些操作。
生成Proxy实例:
var proxy = new Proxy(target, handler);
target参数表示所要拦截的目标对象, handler参数也是一个对象,用来定制拦截行为(get、set等)
var obj = {}
var proxy = new Proxy(obj, {
get(target, property){
console.log(target, property);
return 666;
}
});
proxy.x;
// {} 'x'
// 666
注意:要使Proxy起作用,必须针对Proxy实例( 上文中的proxy而不是obj )进行操作
一种技巧是将原对象的proxy属性设置为自身的Proxy实例,从而可以通过原对象的属性访问代理
Proxy实例还可以作为其他对象的原型对象:
var proxy = new Proxy({}, {
get(target, property){
return 666;
}
});
var obj = Object.create(proxy);
obj.x; //666 因为obj无x属性,所以通过原型链查找到proxy上,访问到proxy.x导致被拦截
1.2 Proxy实例支持的拦截方法
1.2.1 get
get(target , propKey,receiver)
拦截对象属性的读取,比如proxy.foo和proxy['foo']
最后一个参数receiver是一个可选对象, 其指向Proxy对象或者继承于Proxy对象的对象,主要看具体是什么对象发起的get触发了此拦截或者说什么对象会接收此拦截的返回值
receiver一般用于Reflect.get,可以明确this指向
没有this满天飞的情况下,使用target[property]不仅语义化,而且省去了call的开销
但在错综复杂的环境下,想要明确this指向,使用Reflect.get无疑会更安全,具体例子可见下一章笔记06。
例子:数组读取负数索引
var arr = new Proxy([1, 2, 3], {
get(target, key, receiver){
let index = Number(key); //因为key是字符串
if (index<0){
key = String(target.length+index);
}
return Reflect.get(target, key, receiver);
}
})
arr[-1]; //3
若某个属性不可配置(configurable)或不可写(writable),则该属性不能被代理,通过Proxy对象访问时报错
1.2.2 set
set(target, propKey, value, receiver)
拦截某个属性的赋值操作,应当返回布尔值(严格模式下返回false会抛出TypeError异常)
value为赋值操作的值
常常用于检验赋值的合理性、数据绑定
注意:某属性不可写也不可配置时,set不得改变属性的值,只能设置同样的值,否则报错
1.2.3 apply
apply(target, ctx, args)
apply方法拦截函数的调用、call和apply操作
ctx为目标对象的上下文对象(this),args为目标对象的参数数组
var twice = {
apply(target, ctx, args){
return Reflect.apply(...arguments) * 2 // 原计算结果*2
}
}
var proxy = new Proxy((a,b)=>a+b, twice);
proxy(2,3); // 10
proxy.call(null, 2, 3); // 10 此时ctx为null
1.2.4 has
has(target, propKey)
拦截HasProperty操作,即判断对象是否具有某个属性,典型的操作就是in运算符
var handler = {
has(target, key){
if (key[0]==='_'){
return false
}
return key in target
}
}
上述代码实现了对HasProperty操作,隐藏以"_"开头的属性,使其不被in运算符发现
注意:
- 拦截的是HasProperty,而不是HasOwnProperty
- 虽然for in循环用到了in运算符,但has拦截对其无效
1.2.5 construct
construct(target, args, newTarget)
用于拦截new命令,必须返回一个对象
var handler = {
construct(target, args){
return new target(...args);
}
}
1.2.6 deleteProperty
deleteProperty(target, propKey)
用于拦截delete操作,如果该方法抛出错误或者返回false,当前属性就无法被delete命令删除
1.2.7 defineProperty
defineProperty(target, key, descriptor)
descriptor为描述符,该对象的键可以有configurable, enumerable, value, writable, get, set
拦截了Object.defineProperty操作,返回false则无法修改或添加属性
1.3 可取消的Proxy实例
Proxy.revocable方法返回一个可取消的Proxy实例
let {proxy, revoke} = Proxy.revocable(target, handler);
revoke(); // 取消了proxy,此后无法使用proxy来访问
一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束就取消该代理实例,从而收回代理权,不允许再次访问
1.4 this问题
Proxy代理的情况下,目标内部的this关键字会指向代理实例(因为通过代理实例访问的)
此时需要使用合适的方法来访问,比如使用call, bind等等