代理模式

使用 Proxy 对象,我们可以更好地控制与某些对象的交互。代理对象可以在我们与对象交互时确定行为,例如当我们获取值或设置值时。


一般来说,代理是指代他人。

生活中有很多的代理模式的场景。例如,明星有经纪人作为代理,老板有秘书作为代理等等,当有事情的时候,会找到经纪人或秘书,再由他们转达给明星或者老板。

JavaScript 中也是如此:我们将与 Proxy 对象进行交互,而不是直接与目标对象交互。

优点(缺点)

代理是添加对对象行为的控制的强大方法。代理可以有各种用例:它可以帮助验证、格式化、通知或调试。

过度使用Proxy对象或对每个handler方法调用执行繁重的操作很容易对应用程序的性能产生负面影响。最好不要将代理用于性能关键代码。


让我们创建一个person代表 John Doe的对象。

const person = {
  name: 'John Doe',
  age: 42,
  nationality: 'American',
}
1
2
3
4
5

我们希望与代理对象进行交互,而不是直接与此对象交互。在 JavaScript 中,我们可以通过创建Proxy.

const person = {
  name: 'John Doe',
  age: 42,
  nationality: 'American',
}

const personProxy = new Proxy(person, {})
1
2
3
4
5
6
7

的第二个参数Proxy是一个表示_处理程序_的对象。在处理程序对象中,我们可以根据交互的类型定义具体的行为。尽管您可以将许多方法添加到代理处理程序,但最常见的两个是getset

  • get: 尝试访问属性时调用
  • set: 尝试修改属性时调用

实际上,最终会发生以下情况:

person我们将与对象进行交互,而不是直接与对象交互personProxy

让我们将处理程序添加到personProxy代理。当尝试修改属性,从而调用 上的set方法时Proxy,我们希望代理记录该属性的先前值和新值。当试图访问一个属性,从而调用get上的方法时Proxy,我们希望代理记录一个更易读的句子,其中包含属性的键和值。

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`)
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`)
    obj[prop] = value
  },
})
1
2
3
4
5
6
7
8
9

让我们看看当我们尝试修改或检索属性时会发生什么。

personProxy.name
personProxy.age = 43
// The value of name is John Doe
// Changed age from 42 to 43
1
2
3
4

访问该name属性时,代理返回了一个更好听的句子:The value of name is John Doe.

修改age属性时,代理返回此属性的先前值和新值:Changed age from 42 to 43


代理可用于添加验证。用户不应该能够将person’s age 更改为字符串值,或者给他一个空名称。或者如果用户试图访问对象上不存在的属性,我们应该让用户知道。

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        'Hmm.. this property doesn\'t seem to exist on the target object',
      )
    }
    else {
      console.log(`The value of ${prop} is ${obj[prop]}`)
    }
  },
  set: (obj, prop, value) => {
    if (prop === 'age' && typeof value !== 'number') {
      console.log('Sorry, you can only pass numeric values for age.')
    }
    else if (prop === 'name' && value.length < 2) {
      console.log('You need to provide a valid name.')
    }
    else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`)
      obj[prop] = value
    }
  },
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

让我们看看当我们试图传递错误的值时会发生什么!

personProxy.nonExistentProperty
personProxy.age = '44'
personProxy.name = ''
// Hmm.. this property doesn't seem to exist
// Sorry, you can only pass numeric values for age.
// You need to provide a valid name.
1
2
3
4
5
6

代理确保我们没有person用错误的值修改对象,这有助于我们保持数据的纯净!


Reflect

JavaScript 提供了一个名为 的内置对象Reflect,它使我们在使用代理时更容易操作目标对象。

以前,我们尝试通过使用括号表示法直接获取或设置值来修改和访问代理中目标对象的属性。相反,我们可以使用Reflect对象。Reflect对象上的方法与对象上的方法同名handler

我们可以通过和访问或修改目标对象的属性,而不是通过 访问属性obj[prop]或设置属性。这些方法接收与处理程序对象上的方法相同的参数。obj[prop] = value``Reflect.get()``Reflect.set()

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`)
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`)
    Reflect.set(obj, prop, value)
  },
})
1
2
3
4
5
6
7
8
9

我们可以使用对象轻松访问和修改目标对象的Reflect属性。

const person = {
  name: 'John Doe',
  age: 42,
  nationality: 'American',
}

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`)
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`)
    return Reflect.set(obj, prop, value)
  },
})

personProxy.name
personProxy.age = 43
personProxy.name = 'Jane Doe'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Vue中的应用

vue的响应核心也是使用Proxy; 见源码


参考