代理是目标对象的抽象,目标对象既可以直接操作,也可以通过代理对象操作,但是直接操作会绕过代理施予的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 使用 Proxy 创建代理,这个函数接受必须的两个参数,目标对象和处理程序对象
const target = {
id: 1
}
const handler = {}
const proxy = new Proxy(target, handler)
proxy.id = 2
console.log(target.id) // 2
target.id = 3
console.log(proxy.id) // 3
// Proxy.prototype 是 undefined,所以不能使用 instanceof 操作符
target instanceof Proxy // Function has non-object prototype 'undefined' in instanceof check
proxy instanceof Proxy // Function has non-object prototype 'undefined' in instanceof check处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,每次在代理对象上调用这些基本操作时,代理都可以在这些操作被传到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为
所有的捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的行为,但是我们手动的重建被捕获方法的原始行为并不现实,实际开发中我们可以调用
Reflect
对象上的同名方法来完成对捕获行为的原始行为的重建1
2
3
4
5
6
7
8
9
10
11const target = {
id: 1
}
const handler = {
get() {
return Reflect.get(...arguments)
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.id) // 1你也可以基于
Reflect
的样板对捕获的方法进行修饰1
2
3
4
5
6
7
8
9
10
11const target = {
id: 1
}
const handler = {
get() {
return Reflect.get(...arguments) + '!!!'
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.id) // 1!!!捕获器几乎可以改变所有基本方法的行为,但是也有一定的限制,比如一个不可配置,不可写的的数据属性在捕获器返回与其属性不同的值时会抛出异常
代理与目标对象之间的联系时可以断开的,
Proxy
暴露了revocable()
方法,这个方法可以创建一个可撤销的代理对象,需要注意的是,撤销操作是不可逆且幂等的,撤销代理之后再次调用代理会抛出TypeError
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const target = {
id: 1
}
const handler = {
get() {
return Reflect.get(...arguments)
}
}
// 创建可撤销的代理对象
const { proxy, revoke } = Proxy.revocable(target, handler)
console.log(proxy.id) // 1
revoke()
console.log(proxy.id) // TypeError: Cannot perform 'get' on a proxy that has been revoked很多反射 API 方法在对象上都有对象的方法,我们也可以用它来重构对象的方法
1
2
3
4
5
6
7
8
9
10const target = {
id: 1
}
try {
Object.defineProperty(target, 'name', 'VaynePeng')
console.log('success')
} catch (error) {
console.log('error: ', error) // TypeError: Property description must be an object: VaynePeng
}使用
Reflect
进行重构1
2
3
4
5
6
7
8
9const target = {
id: 1
}
if (Reflect.defineProperty(target, 'name', { value: 'VaynePeng' })) {
console.log('success')
} else {
console.log('fail')
}一些常用的反射方法
- 以下方法都会提供状态标记
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.preventExtensions()
Reflect.setPrototypeOf()
Reflect.set()
- 用一等函数替代对象,以下的反射方法提供了只有通过操作符才能完成的操作
Reflect.get()
// 可以替代对象属性访问操作符Reflect.set()
// 可以替代对象属性赋值 = 操作符Reflect.has()
// 可以替代 in 操作符或 with()Reflect.deleteProperty()
// 可以替代 delete 操作符Reflect.construct()
// 可以替代 new 操作符
- 以下方法都会提供状态标记
在通过
apply
方法调用函数时,被调用的函数可能定义了自己的apply
属性,这时我们一般都会使用定义在 Function 原型上的apply
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Function.prototype.apply.call(func, context, args)
func:是要调用的函数
context:是要指定的函数上下文(即 this 值),也就是在函数内部可用的 this 对象
args:是一个数组,包含要传递给函数的参数
const obj = {
value: 10,
}
function method(a, b) {
return this.value + a + b
}
method.apply = function () {
return '自定义apply'
}
method.apply(obj, [1, 2]) // 自定义apply
Function.prototype.apply.call(method, obj, [1, 2]) // 13
// 使用 Reflect 重写
Reflect.apply(method, obj, [1, 2]) // 13代理 Date 类型时,访问某些属性可能报错
1
2
3
4
5const target = new Date()
const proxy = new Proxy(target, {})
console.log(proxy.getDate()) // this is not a Date object代理捕获器和反射方法
代理可以捕获13中不同得基本操作,这些操作有各自不同得反射 API 方法
get()
捕获器会在获取属性值得操作中被调用。对应的反射 API 方法为Reflect.get()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target 目标对象
* @param {string} property 引用的目标对象上的字符串键属性
* @param {*} receiver 代理对象或继承代理对象的对象
* @returns 返回值无限制
*/
get(target, property, receiver) {
console.log('get()')
return Reflect.get(...arguments)
}
})
proxy.foo // get()- 拦截的操作
proxy.property
proxy[property]
Object.create(proxy)[property]
Reflect.get(proxy, property, receiver)
- 捕获器不变式
- 如果
target.property
不可写且不可配置,则处理程序返回的值必须与target.property
匹配。 - 如果
target.property
不可配置且[[Get]]
特性为undefined
,处理程序的返回值也必须是undefined
- 如果
- 拦截的操作
set()
捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为Reflect.set()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target 目标对象
* @param {string} property 引用的目标对象上的字符串键属性
* @param {*} value 要赋给属性的值
* @param {*} receiver 接受最初赋值的对象
* @returns 返回布尔值,true 为成功,false 为失败,返回非布尔值会被转为布尔值
*/
set (target, property, value, receiver) {
console.log('set()')
return Reflect.set(...arguments)
}
})
proxy.foo = 2 // set()- 拦截的操作
proxy.property = value
proxy[property] = value
Object.create(proxy)[property] = value
Reflect.set(proxy, property, value, receiver)
- 捕获器不变式
- 如果
target.property
不可写且不可配置,则不能修改目标属性的值。 - 如果
target.property
不可配置且[[Set]]
特性为undefined
,则不能修改目标属性的值。 - 在严格模式下,处理程序返回
false
会抛出TypeError
- 如果
- 拦截的操作
has()
捕获器会在in
操作符中被调用。对应的反射 API 方法为Reflect.has()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target 目标对象
* @param {string} property 引用的目标对象上的字符串键属性
* @returns 必须返回布尔值,表示属性是否存在,返回非布尔值会被转为布尔值
*/
has(target, property) {
console.log('has()')
return Reflect.has(...arguments)
}
})
'foo' in proxy // has()- 拦截的操作
property in proxy
property in Object.create(proxy)
with(proxy) { (property) }
Reflect.has(proxy, property)
- 捕获器不变式
- 如果
target.property
存在且不可配置,则处理程序必须返回true
- 如果
target.property
存在且目标对象不可扩展,则处理程序必须返回true
- 如果
- 拦截的操作
defineProperty()
捕获器会在Object.defineProperty()
中被调用。对应的反射 API 方法为Reflect.defineProperty()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @param {string} property // 引用的目标对象上的字符串键属性
* @param {*} descriptor// 要定义或修改的属性的描述符
* @returns 必须返回布尔值,表示属性是否成功定义,返回非布尔值会被转为布尔值
*/
defineProperty (target, property, descriptor) {
console.log('defineProperty()')
return Reflect.defineProperty(...arguments)
}
})
Object.defineProperty(proxy, 'bar', { value: 2 }) // defineProperty()- 拦截操作
Object.defineProperty(proxy, property, descriptor)
Reflect.defineProperty(proxy, property, descriptor)
- 捕获器不变式
- 如果目标对象不扩展,则无法定义属性
- 如果目标对象有一个可配置的属性,则不能添加同名的不可配置的属性
- 如果目标对象有一个不可配置的属性,则不能添加同名的可配置的属性
- 拦截操作
getOwnPropertyDescriptor()
捕获器会在Object.getOwnPropertyDescriptor()
中被调用,对应的反射 API 方法为Reflect.getOwnPropertyDescriptor()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @param {string} property // 引用的目标对象上的字符串键属性
* @returns 必须返回对象,或者在属性不存在时返回 undefined
*/
getOwnPropertyDescriptor (target, property) {
console.log('getOwnPropertyDescriptor()')
return Reflect.getOwnPropertyDescriptor(...arguments)
}
})
Object.getOwnPropertyDescriptor(proxy, 'foo') // getOwnPropertyDescriptor()- 拦截操作
Object.getOwnPropertyDescriptor(proxy, property)
Reflect.getOwnPropertyDescriptor(proxy, property)
- 捕获器不变式
- 如果自有的
target.property
存在且不可配置,则处理程序必须返回一个表示该属性存在的对象 - 如果自有的
target.property
存在且可配置,则处理程序必须返回一个表示该属性可配置的对象 - 如果自有的
target.property
存在且target
不可扩展,则处理程序必须返回一个表示该对象存在的对象 - 如果
target.property
不存在且target
不可扩展,则处理程序必须返回undefined
表示该属性不存在 - 如果
target.property
不存在,则处理程序不能返回表示该属性可配置的对象
- 如果自有的
- 拦截操作
deleteProperty()
捕获器会在delete
操作符中被调用,对应的反射 API 方法为Reflect.deleteProperty()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @param {string} property // 引用的目标对象上的字符串键属性
* @returns 必须返回布尔值,表示删除属性是否成功,返回非布尔值会被转化为布尔值
*/
deleteProperty (target, property) {
console.log('deleteProperty()')
return Reflect.deleteProperty(...arguments)
}
})
delete proxy.foo // deleteProperty()- 拦截操作
delete proxy.property
delete proxy[property]
Reflect.deleteProperty(proxy, property)
- 捕获器不变式
- 如果自有的
target.property
存在且不可配置,则处理程序不会删除这个属性
- 如果自有的
- 拦截操作
ownKeys()
捕获器会在Object.keys()
及类似方法中被调用,对应的反射 API 方法为Reflect.ownKeys()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @returns 必须返回包含字符串或者 Symbol 的可枚举对象
*/
ownKeys (target) {
console.log('ownKeys()')
return Reflect.ownKeys(...arguments)
}
})
Object.keys(proxy) // ownKeys()- 拦截操作
Object.getOwnPropertyNames(proxy)
返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)Object.getOwnPropertySymbols(proxy)
返回一个包含给定对象所有自有 Symbol 属性的数组Object.keys(proxy)
返回一个由给定对象自身的可枚举的字符串键属性名组成的数组Reflect.ownKeys(proxy)
返回一个由目标对象自身的属性键组成的数组,包含 Symbol 键
- 捕获器不变式
- 返回的可枚举对象必须包含
target
的所有不可配置的自有属性,如果target
不可扩展,则返回可枚举对象必须准确的包含自有属性键
- 返回的可枚举对象必须包含
- 拦截操作
getPrototypeOf()
捕获器会在Object.getPrototypeOf()
中被调用,对应的反射 API 方法为Reflect.getPrototypeOf()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @returns 必须返回对象或者null
*/
getPrototypeOf (target) {
console.log('getPrototypeOf()')
return Reflect.getPrototypeOf(...arguments)
}
})
Object.getPrototypeOf(proxy) // getPrototypeOf()- 拦截操作
Object.getPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
proxy.__proto__
Object.prototype.isPrototypeOf(proxy)
proxy instanceof Object
- 捕获器不变式
- 如果
target
不可扩展,则Object.getPrototypeOf(proxy)
唯一有效的返回值就是Object.getPrototypeOf(proxy)
的返回值
- 如果
- 拦截操作
setPrototypeOf()
捕获器会在Object.setPrototypeOf()
中被调用,对应的反射 API 方法为Reflect.setPrototypeOf()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @param {*} prototype // target 的替代原型,如果是顶级原型则为 null
* @returns 必须返回布尔值,表示原型赋值是否成功,返回非布尔类型会转化为布尔类型
*/
setPrototypeOf(target, prototype) {
console.log('setPrototypeOf()')
return Reflect.setPrototypeOf(...arguments)
}
})
Object.setPrototypeOf(proxy) // setPrototypeOf()- 拦截操作
Object.setPrototypeOf(proxy)
Reflect.setPrototypeOf()
- 捕获器不变式
- 如果
target
不可扩展,则唯一有效的prototype
参数就是Object.getPrototypeOf(proxy)
的返回值
- 如果
- 拦截操作
isExtensible()
捕获器会在Object.isExtensible()
中被调用,对应的反射 API 方法为Reflect.isExtensible()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @returns 必须返回布尔值,表示 target 是否可扩展,返回非布尔类型会转化为布尔类型
*/
isExtensible(target) {
console.log('isExtensible()')
return Reflect.isExtensible(...arguments)
}
})
Object.isExtensible(proxy) // isExtensible()- 拦截操作
Object.isExtensible(proxy)
Reflect.isExtensible(proxy)
- 捕获器不变式
- 如果
target
可扩展,则值处理程序必须返回 true - 如果
target
不可扩展,则值处理程序必须返回 false
- 如果
- 拦截操作
preventExtensions()
捕获器会在Object.preventExtensions()
中被调用,对应的反射 API 方法为Reflect.preventExtensions()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = {
foo: 1
}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @returns 必须返回布尔值,表示 target 是否已经不可扩展,返回非布尔类型会转化为布尔类型
*/
preventExtensions(target) {
console.log('preventExtensions()')
return Reflect.preventExtensions(...arguments)
}
})
Object.preventExtensions(proxy) // preventExtensions()- 拦截操作
Object.preventExtensions(proxy)
// 将对象变得不可扩展Reflect.preventExtensions(proxy)
- 捕获器不变式
- 如果
Object.isExtensible(proxy)
是 false,则处理程序必须返回 true
- 如果
- 拦截操作
apply()
捕获器会在调用函数时被调用,对应的反射 API 方法为Reflect.apply()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = () => {}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标对象
* @param {*} thisArg// 调用函数时的 this 参数
* @param {*} argumentsList// 调用函数时的参数列表
* @returns 无限制
*/
apply(target, thisArg, ...argumentsList) {
console.log('apply()')
return Reflect.apply(...arguments)
}
})
proxy() // apply()- 拦截操作
proxy()
Funtion.prototype.apply(thisArg, argumentsList)
Funtion.prototype.call(thisArg, ...argumentsList)
Reflect.apply(target, thisArg, argumentsList)
- 捕获器不变式
target
必须是一个函数对象
- 拦截操作
construct()
捕获器会在new
操作符中被调用,对应的反射 API 方法为Reflect.construct()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const target = function() {}
const proxy = new Proxy(target, {
/**
* @param {*} target // 目标构造函数
* @param {*} argumentsList // 传给构造函数的参数
* @param {*} newTarget// 最初被调用的构造函数这里指的是 proxy
* @returns 必须返回一个对象
*/
construct(target, argumentsList, newTarget) {
console.log('construct()')
return Reflect.construct(...arguments)
}
})
new proxy() // construct()- 拦截操作
new proxy()
Reflect.construct(target, argumentsList, newTarget)
- 捕获器不变式
target
必须可以用作构造函数
- 拦截操作