ES6-Proxy

概述

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种元编程.Proxy可以译为代理器.

1
2
3
4
5
6
7
8
9
10
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}`)
return Reflect.get(target,key,receiver)
},
set:function (target,key,value,receiver) {
console.log(`setting ${key}`)
return Reflect.set(target,key,value,receiver)
}
})

上面的代码对一个空对象进行了拦截,重新定义了属性的读取和设置行为.读取或设置了对象obj的属性就会有下面的输出

1
2
3
4
5
obj.count = 1
// setting count
++obj.count
// getting count
// setting count

Proxy实际上重载了点运算符,用自己的定义覆盖了语言的原始定义

Proxy构造函数:

1
var proxy = new Proxy(target, handler)

Proxy对象的所有用法都是这个形式,不同的只是handler参数的写法.target表示的是要拦截的目标对象,handler也是一个对象,用来定义拦截行为.

另一个拦截读取属性行为的例子:

1
2
3
4
5
6
var proxy = new Proxy({}, {
get: (target,property) => {
return 35
}
})
console.log(proxy.age, proxy.name, proxy.title) // 35 35 35

上面的代码中,配置对象get方法用来拦截对目标对象属性的访问请求,get方法的两个参数分别是目标对象和要访问的属性.

可以将Proxy对象设置到object.proxy属性,从而可以在object对象上调用

1
2
3
4
5
6
7
8
9
var object = {
proxy: new Proxy({}, {
get: (target, property) => {
return 35
}
})
}
console.log(object.proxy.a)
// 35

Proxy实例也可以作为其他对象的原型对象

1
2
3
4
5
6
7
8
9

var proxy = new Proxy({}, {
get: (target, property) => {
return 35
}
})
let obj = Object.create(proxy)
console.log(obj.a)
// 35

对于可以设置但是没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果

Proxy实例的方法

get()

get方法用于拦截某个属性的读取操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
name: '张三'
}
var proxy = new Proxy(person, {
get: (target, property) => {
if (property in target) {
return target[property]
} else {
throw new ReferenceError(`Property "${property}" does not exist`)
}
}
})
console.log(proxy.name) // 张三
console.log(proxy.age) // Uncaught ReferenceError: Property "age" does not exist

上面的代码,如果访问目标对象不存在的属性,会抛出一个错误,如果没有这个拦截器,访问不存在的属性只会返回undefine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var pipe = (function () {
return (value) => {
var funcStack = []
var oproxy = new Proxy({}, {
get: (pipeObject, fnName) => {
if (fnName == 'get') {
return funcStack.reduce((val, fn) => {
return fn(val)
}, value)
}
funcStack.push(window[fnName])
return oproxy
}
})
return oproxy
}
}())
var double = n => n * 2
var pow = n => n * n
var reverseInt = n => n.toString().split("").reverse().join("")
pipe(3).double.pow.reverseInt.get

设置Proxy后可以达到链式调用函数

set()

set方法用于拦截某个属性的赋值操作

假定Person对象有一个age属性,该属性应该是不大于200的整数,可以使用Proxy对象保证age的属性值符合要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let validator = {
set: (obj, prop, value) => {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer!')
} else if (value > 200) {
throw new RangeError('The age seems invalid')
}
}
obj[prop] = value
}
}
let person = new Proxy({}, validator)
person.age = 100
person.age = "100" // 报错
person.age = 201 // 报错

有时我们会在对象上设置内部属性,属性名的第一个字符是下划线,这些属性不应该被外部使用,结合get和set方法,可以做到内部属性被外部读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var handler = {
get: (target, key) => {
invariant(key, 'get')
return target[key]
},
set: (target, key, value) => {
invariant(ket, 'set')
target[key] = value
return true
},
}
var invariant = (key, action) => {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private ${key} property`)
}
}
var target = {}
var proxy = new Proxy(target, handler)
proxy._prop
// Uncaught Error: Invalid attempt to get private _prop property
proxy._prop = 1
// Uncaught Error: Invalid attempt to set private _prop property

apply()

apply方法拦截函数的调用,call,apply操作

apply方法接受三个参数:目标对象,目标对象的上下文对象(this)和目标对象的参数数组

1
2
3
4
5
6
7
8
9
var target = () => { return "I am the target" }
var handler = {
apply() {
return "I am the proxy"
}
}
var proxy = new Proxy(target, handler)
console.log(proxy())
// I am the proxy

has()

has方法用来拦截HasProperty操作,即判断对象是否具有具有某个属性时,这个方法会生效,典型的操作就是 in 运 算符

1
2
3
4
5
6
7
8
9
10
11
var handler = {
has(target, key) {
if (key[0] === '_') {
return false
}
return key in target
}
}
var target = { _prop: 'foo', prop: 'bar' }
var proxy = new Proxy(target, handler)
console.log('_prop' in proxy) // false

另外,虽然for…in循环也运用到了 in 运算符,但是has拦截对for…in循环不生效

construct()

construct方法用于拦截new命令,下面是拦截对象的写法

1
2
3
4
5
6
7
8
9
var p = new Proxy(function () { }, {
construct: (target, args) => {
console.log('called:' + args.join(','))
return { value: args[0] * 10 }
}
})
console.log((new p(1)).value)
// called:1
// 10

construct方法返回的必须是一个对象,否则会报错