proxy 对象浅谈

有什么用

Proxy类用于创建js对象的代理。proxy实例可以重新定义对象的基本操作,可以用来拦截赋值,防止调用等。

怎么用

Proxy是一个构造器,在js中通过new关键字来创建实例。构造器接受两个参数:

  1. target

    用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理,不可以为null,字符串,数字,布尔型,Symbol,undefuned)。

  2. handler

    一个定义了被代理对象执行操作时的行为的对象。

一些例子

这里用于展示基本的set和get捕获器的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const obj = {
a: 111,
b: 222,
c: 333,
}

obj.a // 111

const proxy = new Proxy(obj, {
get: (target, prop) => {
if (prop !== 'c') {
return 'proxy yes!'
}
return target[prop]
},
set: (target, prop, value) => {
if (prop === 'a') {
target[prop] = value
return true
}
return true
},
})

proxy.a // proxy yes!

proxy.c // 333

proxy.a = 222

obj.a // 222

proxy.b = 333

obj.b // 222

可以看到,代理的实例拦截了对原对象的取值和赋值操作。当然proxy还提供了很多其他的捕获器,如 applyconstructdefinePropertydeletePropertyhas。。。等等这些,你都可以在这里找到对应的文档:处理器对象 MDN

应用场景

Proxy可以用来实现叫做代理模式的软件开发模式。你可以点击这里了解一下代理模式:代理模式。当然这一篇科普你可能看不懂,看不懂也没关系。建单表述一下,代理就是帮你访问目标实例的中间层

js的代理和传统的代理模式略有不同,我们可以通过Proxy类方便的创建一个对象的代理,然后对外发布。在这个对象的代理实例中可以简单的控制对象的访问权限,或者帮你访问到你无法/无权访问的实例。下面给出一些我在业务场景中用到Proxy类的例子:

Vue Provide

Vue 在 ^2.2 版本增加了 provide / inject 特性,可以跨多层子孙组件传递祖先组件中的一些状态。本身provide并不是响应式的。如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。不过有些data属性并不是组件在实例化时就已经存在的,这时候如果你想让子孙组件通过响应式更新这个provide,就必须在外面再套一层,导致用起来结构就比较复杂。这时候可以通过proxy来解决:

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<template>
<div>
<sub-component></sub-component>
</div>
</template>

<script>
import SubComponent from './components/sub-component.vue'

function genProxy(context, dataName) {
// 这里将一个空对象作为代理的原始target传入,将操作映射到真实的target上。
return new Proxy({}, {
get: (t, propName) => {
if (context[dataName]) {
return context[dataName][propName]
}
return null
},
set: (t, propName, value) => {
if (context[dataName]) {
context[dataName][propName] = value
}
return true
},
})
}

export default {
components: {
SubComponent,
},

data() {
return {
asyncData: null,
}
},

provide() {
return {
provideData: genProxy(this, 'asyncData'),
}
},

created() {
setTimeout(() => {
this.asyncData = {
title: 'yourwilddad',
subTitle: 'other props',
}
}, 5000)
},
}
</script>

子孙组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h1>{{ provideData.title }}</h1>
<h2>{{ provideData.subTitle }}</h2>
</div>
</template>
<script>
export default {
name: 'sub-component',

inject: [
'provideData',
],
}
</script>

这样,在子孙组件中注入了祖先组件的provideData后,仍能在asyncData被赋值后响应式更新自己的内容。

多层代理

在Proxy官方解释中有这么一句话:用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理),当我们代理的targer还是一个代理时,就可以变成一个代理串,每一个代理实例都可以直接使用,也可以拿来生成另一个代理。

我在业务中用来创建了一个axios配置代理,每一层代理实例都可以用来发送xhr请求,当一部分后端接口增加了额外的选项(比如部分接口需要传递额外的header,或者拥有其他的baseURL),就可以用之前的代理实例生成一个新的代理实例,覆盖掉这部分配置。从而减少axios请求部分的代码量。降低code复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { extend } from 'lodash'

/**
* Axios实例代理
* @param {AxiosInstance} axiosInstance Axios 实例
* @param {AxiosRequestConfig} options 要覆盖的配置项
*/
function axiosProxy(axiosInstance, options) {
return new Proxy(axiosInstance, {
get(target, name) {
if (['delete', 'get', 'head', 'options'].includes(name)) {
return (url, config) => target.request(extend({}, options, config || {}, {
method: name,
url: url,
}))
}
if (['post', 'put', 'patch'].includes(name)) {
return (url, data, config) => target.request(extend({}, options, config || {}, {
method: name,
url: url,
data: data,
}))
}
if (name === 'request') {
return (url, config) => {
if (typeof url === 'string') {
return target.request(extend({}, options, config || {}, {
url: url,
}))
}
return target.request(extend({}, options, url || {}))
}
}
return target[name]
},

apply(target, thisArg, argumentsList) {

if (typeof argumentsList[0] === 'string') {
return target.request(extend({}, options, argumentsList[1] || {}, {
url: argumentsList[0],
}))
}

return target.request(extend({}, options, argumentsList[0] || {}))
},
})
}

export default axiosProxy

拥有了这个功能的axios用起来像是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import axios from 'axios'
import genProxy from './axios-proxy'

const config = {
// some options
}

const axiosClient = axios.create(config)

const proxy1 = genProxy(axiosClient, {
baseURL: '//new_base_url'
})

const proxy2 = genProxy(proxy1, {
headers: { 'test-header': 'test' }
})

// get some/path/1
axiosClient.get('some/path/1')

// get //new_base_url/some/path/2
proxy1.get('some/path/2')

// post //new_base_url/some/path/3 with custom header [test-header]
proxy2({ url: 'some/path/3', method: 'post', data: { test: 'test2' } })

兼容性

写在最后

Proxy类通过简单的方式帮我们创建任意对象的代理,他在没有Proxy时上面的这些操作并不是不能完成,只不过Proxy通过更简单的方法让我们实现这些功能。当然还有很多有趣的逻辑都可以通过Proxy实现。具体怎么做,就需要各位看官自己发掘了。当然你们如果有好的想法,非常欢迎在下面留言教教我~

谢谢观看!