title: ES6 - Proxy
date: 2020.12.13
top:
tags:
概述
Proxy 实例的方法
Proxy.revocable()
this 问题
实例:Web 服务的客户端
2020.12.13 星期日 17:33
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler没有设置任何拦截,那就等同于直接通向原对象。
一个技巧是将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用。var object = { proxy: new Proxy(target, handler) };
Proxy 实例也可以作为其他对象的原型对象。let obj = Object.create(proxy);
同一个拦截器函数,可以设置拦截多个操作。
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
下面是 Proxy 支持的拦截操作一览,一共 13 种。
get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
get方法可以继承。
上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。
下面的例子使用get拦截,实现数组读取负数的索引。
利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。
下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。
利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
结合get和set方法,就可以做到防止这些内部属性被外部读写。
上面代码中,set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,请看下面的例子。
注意,如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
注意,严格模式下,set代理如果没有返回true,就会报错。
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
var target = function () { return 'I am the target'; };
var handler = {
apply: function () {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p()
// "I am the proxy"
// 另外,直接调用Reflect.apply方法,也会被拦截。
Reflect.apply(proxy, null, [9, 10]) // 38
has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has()方法可以接受两个参数,分别是目标对象、需查询的属性名。
下面的例子使用has()方法隐藏某些属性,不被in运算符发现。
另外,虽然for…in循环也用到了in运算符,但是has()拦截对for…in循环不生效。
construct()方法用于拦截new命令,下面是拦截对象的写法。
另外,由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
defineProperty()方法拦截了Object.defineProperty()操作。
getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
Object.prototype.proto
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
isExtensible()方法拦截Object.isExtensible()操作。
ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for…in循环
for…in循环也受到ownKeys()方法的拦截。
注意,使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回。
目标对象上不存在的属性
属性名为 Symbol 值
不可遍历(enumerable)的属性
另外,如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。
preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。
注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型。
Proxy.revocable()方法返回一个可取消的 Proxy 实例。
Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
javascript
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.
// 上面代码中,getDate()方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。
// 这时,this绑定原始对象,就可以解决这个问题。
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
javascript
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 (请打开浏览器控制台以查看运行结果。)Object.create(proto,[propertiesObject])
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
18:02