点击这里前往Github获取本文源码,文件名对应着章节名。
本文是我先学习MDN之后写下的,但是实现功能的代码都是我自己根据学习所得知识写出来的,不会照搬。
Proxy
是es6为我们新增的对象,和它的名字一样,它起到一个代理作用,可以拦截对对象的操作,比如获取数据,设置属性等。
下面是从MDN摘过来的,但是释义是我自己写的:
const p = new Proxy(target, handler)
target
被Proxy
代理的对象,或者叫原始对象。
handler
定义了各种函数属性的对象,拦截捕捉对target
对象的各种行为。
这里只说一些表层的方法,像捕捉Object.getPrototypeOf
的handler.getPrototype
这种较为底层的方法就不再进行说明了,因为那些大概是只有开发类库的时候才会用到。
所有的Reflect.xxx
都相当于对应操作的默认行为,因为我们的目的是拦截操作,而非改变这个操作的行为,比如set
就是设置属性,不能把属性删除了,所以在每个拦截函数中都调用了对应在Reflect
对象上的API。
拦截in
操作符。
target
原始对象。
key
被检测属性的键。
下面示例的作用是过滤掉对私有属性的检测,即以下划线开头的属性:
const target = {
foo: 1,
_bar: 2,
}
const p = new Proxy(target, {
has(target, key) {
if (key[0] === '_') {
return false
}
return Reflect.has(target, key)
}
})
console.log('foo' in p)
// true
console.log('_bar' in p)
// false
拦截获取属性的操作。
target
原始对象。
key
被获取属性的键。
receiver
Proxy
对象或者从原型链上获取时的对象,下方有代码解释。
Object.defineProperty
给原始对象赋值时,存在一定约束,详情请见MDN,因为本文只说基础应用。
下面示例的作用是禁止获取私有属性,即以下划线开头的属性:
const target = {
foo: 1,
_bar: 2,
}
const p = new Proxy(target, {
get(target, key, receiver) {
if (key[0] === '_') {
return undefined
}
return Reflect.get(target, key, receiver)
}
})
console.log(p.foo)
// 1
console.log(p._bar)
// undefined
此外,我们再探究一下receiver
,这里的receiver
和下方函数的receiver
逻辑相同:
let obj
const p = new Proxy({}, {
get(target, key, receiver) {
if (receiver === obj) {
console.log('receiver is obj')
} else if (receiver === p) {
console.log('receiver is p')
}
return undefined
}
})
obj = Object.create(p)
p.foo
// receiver is p
obj.foo
// receiver is obj
当我们访问p
时, p
这个代理本身被传入receiver
; 当我们访问obj.foo
时, 这时继承p
的obj
被传入receiver
。
拦截设置属性的操作。
target
原始对象。
key
被设置属性的键。
value
被设置属性的值。
receiver
与get
中的receiver
相同。
下面示例的作用是禁止设置私有属性:
'use strict'
const target = {
foo: 1,
_bar: 2
}
const p = new Proxy(target, {
set(target, key, value, receiver) {
if (key[0] === '_') {
return false
}
return Reflect.set(target, key, value, receiver)
}
})
p.foo = 10
p._bar = 20
// TypeError: 'set' on proxy: trap returned falsish for property '_bar'
只有当严格模式下,即'use strict'
,返回false
时才会引发TypeError
,但无论什么模式,这里的_bar
都不会被成功赋值。
拦截delete
操作符。
target
原始对象。
key
被删除属性的值。
target
上的某属性是用Object.defineProperty
定义为不可配置,那么该属性无法被删除,其实这个地方不需要我们太留意,因为Reflect.deleteProperty
不会让这种操作成功进行。
下面示例的作用是禁止删除私有属性:
'use strict'
const target = {
foo: 1,
_bar: 2,
}
const p = new Proxy(target, {
deleteProperty(target, key) {
if (key[0] === '_') {
return false
}
return Reflect.deleteProperty(target, key)
}
})
delete p.foo
delete p._bar
// TypeError: 'deleteProperty' on proxy: trap returned falsish for property '_bar'
与set
相同,这里要开启严格模式才会报错。
拦截调用操作。
注意,target
本身应该就是一个函数,如果不是在调用时会直接抛出一个TypeError
。
target
原始函数,注意它应该是一个函数。
thisArg
原来调用target
时this
应该是谁,它就是谁。
args
传进来的参数,是一个数组。
下面示例的作用是把原函数返回值都取反:
function sum(a, b) {
return a + b
}
const p = new Proxy(sum, {
apply(target, thisArg, args) {
return -Reflect.apply(target, thisArg, args)
}
})
console.log(sum(1, 2))
// 3
console.log(p(1, 2))
// -3
拦截new
操作符。
注意这里的target
本身就应该是一个构造函数或类,它可以被new
调用,否则会直接抛出TypeError
。
target
原始构造函数。
args
new
时传入的参数。
newTarget
类似set
和get
的receiver
,下面有解释。
下面示例的作用是把所有传入的参数都乘10:
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
const PointProxy = new Proxy(Point, {
construct(target, args, newTarget) {
args = args.map(n => n * 10)
return Reflect.construct(target, args, newTarget)
}
})
关于newTarget
到底是谁,下面做一个探究:
class Foo {}
const FooProxy = new Proxy(Foo, {
construct(target, args, newTarget) {
console.log(newTarget)
return Reflect.construct(target, args, newTarget)
}
})
class Bar extends FooProxy {}
new FooProxy()
// [class Foo]
new Bar()
// [class Bar extends Foo]
也就是说你new
的是谁,这里的newTarget
就是谁。
参数与new Proxy
时相同,但会返回一个proxy
对象和revoke
函数:
const { proxy, revoke } = Proxy.revocable(target, handler)
这里的revoke
可以让proxy
不可用,通常应用于只允许用户访问proxy
对象且访问结束后就要销毁,从而不让用户把proxy
对象保存下来以后再次访问。
const { proxy, revoke } = Proxy.revocable({}, {})
proxy.foo = 1
console.log(proxy.foo)
// 1
revoke()
console.log(proxy.foo)
// TypeError: Cannot perform 'get' on a proxy that has been revoked