JavaScript中的Proxy

前言

这个和之前的Reflect有一定的关系,也是Vue3实现的一个很重要的API,学学学。

Proxy

顾名思义,就是一个代理对象,通过一组配置函数来拦截操作从而使得以源对象得以被扩展。

Proxy是一个构造函数

由两个参数

  • obj 需要代理的对象
  • handler 一组由特定的处理函数组成的对象,如果没有指定,则会使用源对象的默认行为。
1
2
3
var o = {}

var proxy = new Proxy(o, {})

在Reflect上的方法,Proxy都可以拦截,具体包括

  • getPrototypeOf
  • setPrototypeOf
  • isExtensible
  • preventExtensible
  • getOwnPropertyDescriptor
  • defineProperty
  • has
  • get
  • set
  • deleteProperty
  • ownKeys
  • apply
  • construct

getPrototypeOf()

读取代理对象的原型时,这个方法就会调用

有一个参数

  • obj 源对象

该函数必须返回一个对象或者null

1
2
3
4
5
6
7
8
9
10
11
12
13
var o = {}

var prototype = {
name: 'prototype',
}

var proxy = new Proxy(o, {
getPrototypeOf(target) {
return prototype
},
})

console.log(Reflect.getPrototypeOf(proxy) === prototype) // 打印true

可以触发该函数的操作包括

  • Object.getPrototypeOf
  • Reflect.getPrototypeOf
  • __proto__
  • Object.prototype.isPrototypeOf
  • instanceof
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var proxy = new Proxy(
{},
{
getPrototypeOf(target) {
console.log('调用了')
return Reflect.getPrototypeOf(target)
},
},
)

// 打印了5次"调用了"
Object.getPrototypeOf(proxy)
Reflect.getPrototypeOf(proxy)
proxy.__proto__
Object.prototype.isPrototypeOf(proxy)
proxy instanceof Object

setPrototypeOf()

设置代理对象的原型时,这个方法就会调用。

有两个参数

  • obj 源对象
  • prototype 将设置的原型对象

该函数在设置prototype成功返回true,失败返回false

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
setPrototypeOf(obj, prototype) {
console.log('设置了prototype')
return Reflect.setPrototypeOf(obj, prototype)
},
},
)

Reflect.setPrototypeOf(proxy, {})

可以触发该函数的操作包括

  • Reflect.setProtytpeOf
  • Object.setPrototypeOf

isExtensible()

查询代理对象是否可以扩展时,这个方法就会调用。

有一个参数

  • obj 源对象

返回值必须为一个boolean值或者可以转为Boolean的值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
isExtensible(obj) {
console.log('查询了是否能够扩展')
return Reflect.isExtensible(obj)
},
},
)

Reflect.isExtensible(proxy)

可以触发该函数的操作包括

  • Reflect.isExtensible
  • Object.isExtensible

preventExtensions()

禁止代理对象扩展时,这个方法就会调用。

有一个参数

  • obj 源对象

返回值必须为一个boolean值或者可以转为boolean的值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
preventExtensions(obj) {
console.log('禁止了扩展')
return Reflect.preventExtensions(obj)
},
},
)

Reflect.preventExtensions(proxy)

可以触发该函数的操作包括

  • Reflect.preventExtensions
  • Object.preventExtensions

getOwnPropertyDescriptor()

获取对象属性的属性描述符时,这个方法会调用。

有两个参数

  • obj 源对象
  • propertyKey 需要获取属性描述符的属性名

返回值必须为一个对象或者undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
var proxy = new Proxy(
{
name: 'lwf',
},
{
getOwnPropertyDescriptor(obj, propertyKey) {
console.log('获取了属性描述符')
return Reflect.getOwnPropertyDescriptor(obj, propertyKey)
},
},
)

Reflect.getOwnPropertyDescriptor(proxy, 'name') // 打印

可以触发该函数的操作包括

  • Reflect.getOwnPropertyDescriptor
  • Object.getOwnPropertyDescriptor

defineProperty()

在定义代理对象的属性时,这个方法会调用。

有三个参数

  • obj 源对象
  • propertyKey 将设置的属性名
  • descriptor 将设置属性的属性描述符

返回值必须为一个boolean值或者可以转为boolean的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var proxy = new Proxy(
{},
{
defineProperty(obj, propertyKey, descriptor) {
console.log('设置了对象的属性')
return Reflect.defineProperty(obj, propertyKey, descriptor)
},
},
)

Reflect.defineProperty(proxy, 'name', {
value: 'lwf',
}) // 打印

console.log(proxy.name)

可以触发该函数的操作包括

  • Reflect.defineProperty
  • Object.defineProperty
  • proxyObj.propertyKey = value

has()

在检查属性是否存在对象中时,这个方法会调用。

有两个参数

  • obj 源对象
  • propertyKey 需要检查的属性名

返回值必须为一个boolean值或者可以转为boolean的值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
has(obj, propertyKey) {
console.log('检查了属性是否存在')
return propertyKey in obj
},
},
)

'name' in proxy // 打印

可以触发该函数的操作包括

  • propertyKey in proxy
  • propertyKey in Object.create(proxy)
  • Reflect.has
  • with操作

get()

在读取代理对象属性时,这个方法会调用。

有三个参数

  • obj 源对象
  • propertyKey 要获取的属性名
  • context 要绑定到getter的上下文,默认为调用对象本身。

返回值可以为任何值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
get(obj, propertyKey, context) {
console.log('获取了属性值')
return Reflect.get(obj, propertyKey, context)
},
},
)

proxy.name // 打印

可以触发该函数的操作包括

  • proxy[propertyKey]或者proxy.propertyKey
  • Object.create(proxy)[propertyKey]或者Object.create(proxy).propertyKey
  • Reflect.get

set()

在设置代理对象属性值时,这个方法会调用。

有四个参数

  • obj 源对象
  • propertyKey 设置的属性名
  • value 设置的属性值
  • context 要绑定到setter的上下文,默认为调用对象本身。

TIPS:
对于getset,其中context参数的传入通常是proxy对象本身,但是对于处于原型链上的proxy,传入的就不是proxy对象了,而是触发操作的那个对象,比如,obj.propertykey = valueobj不是代理对象,但是obj的原型链上存在代理对象proxy,那传入context参数的就是obj而不是proxy了。而对于通过Reflect.set操作触发,context参数就跟set函数指定的一样了。

返回值必须为一个boolean值或者可以转为boolean的值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
set(obj, propertyKey, value, context) {
console.log('调用了设置值')
return Reflect.set(obj, propertyKey, value, context)
},
},
)

proxy.name = 'lwf' // 打印

可以触发该函数的操作包括

  • proxy[propertyKey] = value或者proxy.propertyKey = value
  • Object.create(proxy)[propertyKey] = value或者Object.create(proxy).propertyKey = value
  • Reflect.set

deteleProperty()

在删除对象的属性时,这个方法会调用

有两个参数

  • obj 源对象
  • propertyKey 将删除的属性名

返回值必须为一个boolean值或者可以转为boolean的值。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
deleteProperty(obj, propertyKey) {
console.log('删除了属性')
return Reflect.deleteProperty(obj, propertyKey)
},
},
)

delete proxy.name // 打印

可以触发该函数的操作包括

  • delete proxy[propertyKey]或者delete proxy.propertyKey
  • Reflect.deleteProperty

ownKeys()

获取代理对象属性名组成的数组时,这个方法会调用

有一个参数

  • obj 源对象

返回值必须为一个可枚举的对象。

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy(
{},
{
ownKeys(obj) {
console.log('获取了属性名组成的数组')
return Reflect.ownKeys(obj)
},
},
)

Reflect.ownKeys(proxy) // 打印

可以触发该函数的操作包括

  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • Reflect.ownKeys
  • Object.keys

apply()

在对代理对象进行函数调用时,这个方法会调用

有一个参数

  • fn 源函数
  • context 执行函数的上下文
  • args 参数的数组

返回值可以为任意值。

1
2
3
4
5
6
7
8
9
10
11
12
function fn(msg) {
console.log(msg)
}

var proxy = new Proxy(fn, {
apply(fn, context, args) {
console.log('执行了函数')
return fn.apply(context, args)
},
})

proxy('hello world') // 打印

可以触发该函数的操作包括

  • proxy(arg1,arg2,...,argN)
  • Function.prototype.apply或者Function.prototype.call
  • Reflect.apply

construct()

以代理对象为构造器或者原型上存在代理对象的构造器生成对象时,这个方法会调用

有三个参数

  • obj 源对象(可以被new
  • args 参数数组
  • newTarget 被调用的构造函数

返回值必须为一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn(name) {
this.name = name
}

var proxy = new Proxy(fn, {
construct(obj, args, newTarget) {
console.log('构造了新对象')
return Reflect.construct(obj, args)
},
})

var p = new proxy('lwf') // 打印

console.log(p.name) // 打印 "lwf"

可以触发该函数的操作包括

  • new proxy(arg1,arg2,...,argN)
  • Reflect.construct

后记

学完感觉我能看懂vue3源码了