ES6(三)Proxy和Reflect

本文最后更新于:8 个月前

Proxy对象用来设置拦截器,提供对外界访问的过滤和改写的机制。Reflect对象继承和规范了对象操作的API

ES6(三)Proxy和Reflect

一、Proxy代理器

1. 简介

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

1
2
// 语法格式
const proxy = new Proxy(target, handler);
  • target,代理的对象
  • handler,配置对象,用来设置拦截程序

Proxy构造函数返回一个代理器实例,通过这个实例可以实现对原对象(即target)的操作,同时可以自定义一些逻辑,在访问时触发。如果直接访问原对象,将不会触发这些自定义逻辑。

2. 拦截器

Proxy 支持的拦截操作一览,一共 13 种。

  • **get(target, propKey, receiver)**:拦截对象属性的读取,比如proxy.fooproxy['foo']
  • **set(target, propKey, value, receiver)**:拦截对象属性的设置,比如proxy.foo = vproxy['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)

下面重点介绍常用的几种。

get()和set(),分别拦截读取、写操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var init = {a:1}
var obj = new Proxy(init, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
obj.count = 1 // setting count!
init // {a: 1, count: 1}
obj.count++ // getting count! setting count!
init // {a: 1, count: 2}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// get和set也可以不用Reflect.get()、Reflect.set()
var person = {
name: "张三"
};

var proxy = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
},
set: function(target, propKey, value){
console.log(`setting ${propKey}`)
target[propKey] = value
}
});

proxy.name // '张三'
proxy.age // Uncaught ReferenceError: Prop name "age" does not exist.
proxy.sex = 'female' // setting sex
person // {name: '张三', sex: 'female'}

apply(),拦截Proxy示例作为函数调用的操作

1
2
3
4
5
6
7
var handler = {
apply: function(target, thisBinding, args) {
return args[0]
}
}
var fproxy = new Proxy(function(x,y){return x+y},handler)
fproxy(1,2) // 1

construct(),拦截Proxy实例作为构造函数调用的操作

1
2
3
4
5
6
7
var handler = {
construct: function(target, args) {
return {value: args[1]}
}
}
var fproxy = new Proxy(function(x,y){return x+y},handler)
new fproxy(1,2) // {value: 2}

receiver指向原始的(读)操作所在的那个对象,一般就是Proxy实例

1
2
3
4
5
6
7
var init = {a:1}
var proxy = new Proxy(init,{
get:function(target,key,receiver){
return receiver
}
})
proxy.a === proxy // true

3. Proxy.revocable() 可取消的代理器

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

1
2
3
4
5
6
7
8
9
10
let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

4. Proxy实例的this指向

Proxy实例的this指向handler配置对象,这会导致一些错误发生:有些原生对象的内部属性,因为只有通过正确的this才能拿到,如Date对象

1
2
3
4
5
6
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

解决办法是:在拦截器中绑定this指向原生对象

1
2
3
4
5
6
7
8
9
10
11
12
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 1

二、Reflect

1. 简介

Reflect是ES6为了操作对象而提供的新APIReflect对象的设计目的有这样几个:

  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  3. Object操作都变成函数行为
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。

2. 静态方法

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args),等同于new target(...args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name),等同于delete obj[name]
  • Reflect.has(target, name),对应name in obj里面的in运算符
  • Reflect.ownKeys(target),用于返回对象的所有属性
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target),用于读取对象的__proto__属性
  • Reflect.setPrototypeOf(target, prototype)

Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined

1
2
3
4
var myObject = {
foo: 1
}
Reflect.get(myObject, 'foo') // 1

Reflect.set方法设置target对象的name属性等于value

1
2
3
4
5
6
7
8
9
10
11
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}

myObject.foo // 1

Reflect.set(myObject, 'foo', 2);
myObject.foo // 2

如果get()set()给了receiver参数,则读取函数的this绑定receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}

var myReceiverObject = {
foo: 4,
bar: 4,
};

Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'baz') // 3
Reflect.get(myObject, 'baz', myReceiverObject) // 8

ES6(三)Proxy和Reflect
http://timegogo.top/2023/03/28/JavaScript/ES6(三)Proxy和Reflect/
作者
丘智聪
发布于
2023年3月28日
更新于
2023年7月16日
许可协议