Proxy,代理,是ES6新增的功能,可以理解为代理器(即由它代理某些操作)。
Proxy 对象用于定义或修改某些操作的自定义行为,可以在外界对目标对象进行访问前,对外界的访问进行改写。
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。
1. Proxy 定义
1 | var proxy = new Proxy(target, handler) |
new Proxy()
表示生成一个 Proxy 实例
- target:目标对象
- handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。
注意:要实现拦截操作,必须是对 Proxy 实例进行操作,而不是针对目标对象 target 进行操作。
首先,看个例子:
1 | let handler = { |
在这个例子中,proxy 拦截了get和set操作。
再看一个例子:
1 | let handler = { |
则由上面代码看出:Proxy 不仅是拦截了行为,更是用自己定义的行为覆盖了组件的原始行为。
若handler = {}
,则代表 Proxy 没有做任何拦截,访问 Proxy 实例就相当于访问 target 目标对象。这里不再演示,有兴趣的可以自己举例尝试。
2. Proxy handler方法(拦截方法)
get(target, key, receiver)
:拦截 target 属性的读取set(target, key, value, receiver)
:拦截 target 属性的设置has(target, key)
:拦截key in proxy
的操作,并返回是否存在(boolean值)deleteProperty(target, key)
:拦截delete proxy[key]
的操作,并返回结果(boolean值)ownKeys(target)
:拦截Object.getOwnPropertyName(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for ... in
循环。并返回目标对象所有自身属性的属性名数组。注意:Object.keys()
的返回结果数组中只包含目标对象自身的可遍历属性getOwnPropertyDescriptor(target, key)
:拦截Object.getOwnPropertyDescriptor(proxy, key)
,返回属性的描述对象defineProperty(target, key, desc)
:拦截Object.defineProperty(proxy, key, desc)
、Object.defineProperties(proxy, descs)
,返回一个 boolean 值preventExtensions(target)
:拦截Object.preventExtensions(proxy)
,返回一个 boolean 值getPrototypeOf(target)
:拦截Object.getPrototypeOf(proxy)
,返回一个对象isExtensible(target)
:拦截Object.isExtensible(proxy)
,返回一个 boolean 值setPrototypeOf(target, key)
:拦截Object.setPrototypeOf(proxy, key)
,返回一个 boolean 值。如果目标对象是函数,则还有两种额外操作可以被拦截apply(target, object, args)
:拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
construct(target, args)
:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
总共 13 个拦截方法,下面进行简要举例说明,更多可见阮一峰老师的 《ECMAScript 6 入门》
1. get,set
get
方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
set
拦截 target 属性的设置,可以接受四个参数,依次为目标对象、属性名、value和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
1 | let target = {foo: 1} |
2. has
拦截 propKey in proxy 的操作,返回一个布尔值。
1 | // 使用 has 方法隐藏某些属性,不被 in 运算符发现 |
3. ownKeys
拦截自身属性的读取操作。并返回目标对象所有自身属性的属性名数组。具体返回结果可结合 MDN 属性的可枚举性和所有权
Object.getOwnPropertyName(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for ... in
循环
1 | let target = { |
4. apply
apply 拦截 Proxy 实例作为函数调用的操作,比如函数的调用(proxy(...args)
)、call(proxy.call(object, ...args)
)、apply(proxy.apply(...)
)等。
1 | var target = function () { return 'I am the target'; }; |
Proxy 方法太多,这里只是将常用的简要介绍,更多请看阮一峰老师的 《ECMAScript 6 入门》
和Object.defineProperty()的对比
defineProperty存在以下问题:
- 不能监听数组的变化
- 必须遍历对象的每个属性
- 必须深层遍历嵌套的对象(vue walk方法)
也需要嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'cache']
}
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
// 递归创建并返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
// 以下两句都能够进入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')
Proxy妙用
1 | const www = new Proxy(new URL('https://www'), { |
- 访问百度
1
2
3
4
5
6
7
8
9
10www.baidu.com.then(response => {
console.log(response.status);
// ==> 200
})
const response = await www.baidu.com
console.log(response.ok)
// ==> true
console.log(response.status);
// ==> 200