概述

Proxy 用于修改某些操作的默认行为,等用于在语言做出修改,属于一种‘元编程’,即对编程语言进行编程。
Proxy 可以让外界对对象的访问进行过滤和改写。

  1. var obj = new Proxy({}, {
  2. get: function(target, key, receiver) {
  3. return Reflect.get(target, key, receiver)
  4. },
  5. set: function(target, key, receiver) {
  6. return Reflect.set(target, key, value, receiver)
  7. }
  8. })

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

Es6提供Proxy 构造函数,用来生成Proxy 实例。

  1. var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的是handler 参数的写法。
new Proxy() 标识生成一个Proxy 实例,target 参数标识所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

// 拦截读取属性行为

  1. var proxy = new Proxy({}, {
  2. get: function(target, property) {
  3. return 35
  4. }
  5. })
  6. proxy.name // 35
  7. proxy.time // 35
  8. proxy.title // 35

访问任何属性都得到35。要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作。

如果handler没有设置任何拦截,那等同于直接通向原对象。

  1. var target = {}
  2. var handler = {}
  3. var proxy = new Proxy(target, handler);
  4. proxy.a = "b"
  5. target.a // 'b'

一个技巧是将Proxy 对象,设置到 object.proxy 属性,从而可以在object 对象上调用。

  1. var object = {proxy: new Proxy(taret, handler)}

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

  1. var proxy = new Proxy({}, {
  2. get: function(target, property) {
  3. return 35
  4. }
  5. })
  6. let obj = Object.create(proxy)
  7. obj.time // 35

同一个拦截器函数,可以设置拦截多个操作

  1. var handler = {
  2. get: function(target, name) {
  3. if (name === 'prototype') {
  4. return Object.prototype
  5. }
  6. return 'Hello, ' + name
  7. },
  8. apply: function(target, thisBinding, args) {
  9. retrun args[0]
  10. }
  11. construct: function(target, args) {
  12. return {value: args[1]}
  13. }
  14. }
  15. var fproxy = new Proxy(function(x, y) {
  16. return x + y
  17. }, handler)
  18. fproxy(1, 2) // 1
  19. new fproxy(1, 2) // {value: 2}
  20. fproy.prototype === Object.prototype // true
  21. fproxy.foo === 'Hello, foo' // true

Proxy 支持的拦截操作: 13种
get(target, propKey, receiver)
拦截对象属性的读取
set(target, propKey, value, receiver)
拦截对象属性的设置
has(target, propKey)
拦截propKey in proxy的操作, 返回一个布尔值。
deletePropery(target, propkey)
拦截delete proxy[propLKey] 的操作,返回一个布尔值。
ownkeys(target)
getOwnPropertyDescriptor(target, propkey)
difineProperty(target, propKey, propDesc)
preventExtensions(target)
getPrototypeOf(target)
isExtensible(target)
setPrototypeOf(target, proto)
apply(target, object, args)
拦截Proxy 实例作为函数的调用,比如 proxy(…args) proxy.call(object, …args) proxy.apply(…)
construct(target, args)
拦截Proxy实例作为构造函数调用的操作,比如: new proxy(…args)

get()

  1. var person = { name: '张三' }
  2. var proxy = new Proxy(person, {
  3. get: function(target, property) {
  4. if (property in target) {
  5. return target[property]
  6. } else {
  7. console.log(`${property}属性不存在`)
  8. }
  9. }
  10. })
  11. proxy.name // 张三
  12. proxy.age // age属性不存在

get() 可以继承

  1. let proto = new Proxy({}, {
  2. get(target, propertyKey, receiver) {
  3. console.log('GET', propertyKey)
  4. return target[propertyKey]
  5. }
  6. })
  7. let obj = Object.create(proto)
  8. obj.foo // 'GET foo'

利用 Proxy, 可将 读取属性的操作,转变为执行某个函数,从而实现属性的链式操作。

  1. var pipe = (function() {
  2. return function(value) {
  3. var funStack = []
  4. var oproxy = new Proxy({}, {
  5. get: function(popeObject, fnName) {
  6. if (fnName === 'get') {
  7. return funStack.reduce(function(val, fn) {
  8. retrun fn(val)
  9. }, value)
  10. }
  11. funcStack.push(window[fnName])
  12. return oproxy
  13. }
  14. })
  15. return oproxy
  16. }
  17. }())

利用get拦截,实现一个生成各种DOM节点的通用函数dom

set()

set方法用来拦截某个属性的复制操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实例本身(可选)。
假定Person对像的age 属性是一个不大于200的整数

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

set 的第4个参数receiver, 指的是原始的操作行为所在的那个对象,一般情况下proxy实例本身

  1. const handler = {
  2. set: function(obj, prop, value, receiver) {
  3. obj[prop] = receiver
  4. }
  5. }
  6. const proxy = new Proxy({}, handler)
  7. proxy.foo = 'bar'
  8. proxy.foo === proxy // true

注意: 如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。

apply()

apply方法拦截函数的调用、call 和 apply 操作。

apply方法接受三个参数,分别是目标对象、目标对象的上下文对象 和 目标对象的参数数组。

  1. var target = function() { return 'i am the taret'}
  2. var handler = {
  3. apply: function() {
  4. return 'i am the proxy'
  5. }
  6. }
  7. let p = new Proxy(target, handler)
  8. p() // i am the proxy
  1. var twice = {
  2. apply(target, ctx, args) {
  3. return Reflect.apply(... arguments) * 2
  4. }
  5. }
  6. function sum(left, right) {
  7. return left + right
  8. }
  9. var proxy = new Proxy(sum, twice)
  10. proxy(1, 2) // 6
  11. proxy.call(null, 5, 6) // 22
  12. proxy.apply(null, [7, 8]) // 30

has()

has 方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in运算符。
has方法可以接受两个参数,分别时目标对象、需查询的属性名。

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

construct()

construct方法用于拦截new命令。
接受两个参数
target: 目标对象
args: 构造函数的参数对象
newTarget 创造实例对象时, new 命令作用的构造函数

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

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

  1. var p = new Proxy(function() {}, {
  2. contruct: function(target, argumentsList) {
  3. return 1
  4. }
  5. })
  6. new p() // 报错

deleteProperty()

用于拦截delete操作,如果这个方法抛出错误或者返回false,档期那属性就无法被delete命令删除。

  1. var handler = {
  2. deleteProperty(target, key) {
  3. invariant(key, 'delete')
  4. delete target[key]
  5. return true
  6. }
  7. }
  8. function invariant(key, action) {
  9. if (key[0] === '_') {
  10. throw new Error(`Invalid attempt to ${action} private '${key}' property`)
  11. }
  12. }
  13. var target = {_prop: 'foo'}
  14. var proxy = new Proxy(target, handler);
  15. delete proxy._prop
  16. // erro: Invalid attempt to delete private "_prop" property

注意: 目标对象自身的不可配置的属性,不能被deleteProperty方法删除,否则报错

defineProperty()

defineProperty 方法拦截了Object.defineProperty操作。

  1. var handler = {
  2. defineProperty(target, key, descriptor) {
  3. return false
  4. }
  5. }
  6. var target = {}
  7. var proxy = new Proxy(target, handler)
  8. proxy.foo = 'bar' // 不会生效

上例, defineProperty方法返回false,导致添加新属性总是无效。
注意: 如果目标对象不可扩展,的defineProperty不能增加目标对象上不存在的属性,否则会报错。
如果目标对象的某个属性不可写或不可配置,则defineProperty方法不得改变这两个设置。

getOwnPropertyDescriptor()

getOwnPropertyDescriptor 方法拦截 Object.getOwnPropertyDescriptot(), 返回一个属性描述对象或undefined.

  1. var handler = {
  2. getOwnPropertyDescriptor(target, key) {
  3. if (key[0] === '_') return
  4. return Object.getOwnPropertyDescriptor(target, key)
  5. }
  6. }
  7. var target = {_foo: 'bar', baz: 'tar'}
  8. var proxy = new Proxy(target, handler)
  9. Object.getOwnPropertyDescriptor(proxy, 'wat') // undefined
  10. Object.getOwnPropertyDescriptor(proxy, '_foo') // undefined
  11. Object.getOwnPropertyDescriptor(proxy, 'baz') // { value: 'tar', writabel: true, enumerable: true, configurable: true }

getPrototypeOf()
主要用来拦截获取对象原型。如以下操作:

  1. Object.prototype.proto
  2. Object.prototype.isPrototypeOf()
  3. Object.getPrototypeOf()
  4. Reflect.getPrototypeOf()
  5. instanceof

isExtensible()

isExtensible 方法拦截Object.isExtensible操作

  1. var p = new Proxy({}, {
  2. isExtensible: function(target) {
  3. console.lo('called')
  4. return true
  5. }
  6. })
  7. Object.isExtensible(p)
  8. // 'called'
  9. // true

ownKeys()

ownkeys 方法用来拦截对象自身属性的读取操作。

  1. let target = {a: 1, b: 2, c 3}
  2. let handler = {
  3. ownKeys(target) {
  4. return ['a']
  5. }
  6. }
  7. let proxy = new Proxy(target, handler);
  8. Object.keys(proxy)

preventExtensions()

preventExtensions 方法拦截Object.preventExtensions()。
该方法必须返回一个布尔值,否则会被自动转为布尔值。
限制: 只有目标对象不可扩展时(Object.isExtensible(proxy))为false, proxy.preventExtensions 才能返回true,否则会报错。

  1. var proxy = new Proxy({}, {
  2. preventExtensions: function(target) {
  3. return false
  4. }
  5. })
  6. Object.preventExtensions(proxy)
  7. // Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

setPrototypeOf()

setPrototypeOf 方法主要用来拦截Object.setPrototypeOf 方法

  1. var handler = {
  2. setPrototypeOf(target, proto) {
  3. throw new Error('Changing the prototype is forbidden')
  4. }
  5. }
  6. var proto = {}
  7. var target = function() {}
  8. var proxy = new Proxy(target, handler)
  9. Object.setPrototypeOf(proxy, proto)
  10. // Error: changing the prototype is forbidden

Proxy.revocable()

Proxy.revocable 方法返回一个可取消的 Proxy 实例

  1. var target = {}
  2. var handler = {}
  3. let {proxy, revoke} = Proxy.revocalbe(target, handler)
  4. proxy.foo = 123
  5. proxy.foo // 123
  6. revoke()
  7. proxy.foo // TypeError: Revoked

Proxy.revocable 方法返回一个对象,该对象的proxy属性是 Proxy 实例, revoke 属性是一个函数,可以取消Proxy 实例。上面的代码种,当执行revoke 杉树之后,再访问Proxy 实例, 就会抛出一个错误。