JavaScript - 代理与反射(代理模式 + 小结)

使用代理可以在代码中实现一些有用的编程模式。

一、跟踪属性访问

通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:

const user = {
  name: 'Jake'
}
const proxy = new Proxy(user, {
  get (target, property, receiver) {
    console.log(`Getting ${property}`)
    return Reflect.get(...arguments)
  },
  set (target, property, value, receiver) {
    console.log(`Setting ${property}=${value}`)
    return Reflect.set(...arguments)
  }
})

// Getting name
// Jake
console.log(proxy.name)

// Setting age=27
// 27
console.log((proxy.age = 27))

二、隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:

const hiddenProperties = ['foo', 'bar']
const targetObject = {
  foo: 1,
  bar: 2,
  baz: 3
}
const proxy = new Proxy(targetObject, {
  get (target, property) {
    if (hiddenProperties.includes(property)) {
      return undefined
    } else {
      return Reflect.get(...arguments)
    }
  },
  has (target, property) {
    if (hiddenProperties.includes(property)) {
      return false
    } else {
      return Reflect.has(...arguments)
    }
  }
})
// get()
console.log(proxy.foo) // undefined
console.log(proxy.bar) // undefined
console.log(proxy.baz) // 3
// has()
console.log('foo' in proxy) // false
console.log('bar' in proxy) // false
console.log('baz' in proxy) // true

三、属性验证

因为所有赋值操作都会触发 set() 捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

const target = {
  onlyNumbersGoHere: 0
}
const proxy = new Proxy(target, {
  set (target, property, value) {
    if (typeof value !== 'number') {
      return false
    } else {
      return Reflect.set(...arguments)
    }
  }
})
proxy.onlyNumbersGoHere = 1
console.log(proxy.onlyNumbersGoHere) // 1
proxy.onlyNumbersGoHere = '2'
console.log(proxy.onlyNumbersGoHere) // 1

四、函数与构造函数参数验证

跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:

function median (...nums) {
  return nums.sort()[Math.floor(nums.length / 2)]
}
const proxy = new Proxy(median, {
  apply (target, thisArg, argumentsList) {
    for (const arg of argumentsList) {
      if (typeof arg !== 'number') {
        throw 'Non-number argument provided'
      }
    }
    return Reflect.apply(...arguments)
  }
})
console.log(proxy(4, 7, 1)) // 4
console.log(proxy(4, '7', 1))
// Error: Non-number argument provided

类似地,可以要求实例化时必须给构造函数传参:

class User {
  constructor (id) {
    this.id_ = id
  }
}
const proxy = new Proxy(User, {
  construct (target, argumentsList, newTarget) {
    if (argumentsList[0] === undefined) {
      throw 'User cannot be instantiated without id'
    } else {
      return Reflect.construct(...arguments)
    }
  }
})
new proxy(1)
new proxy()
// Error: User cannot be instantiated without id

五、数据绑定与可观察对象

通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。
比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:

const userList = []
class User {
  constructor (name) {
    this.name_ = name
  }
}
const proxy = new Proxy(User, {
  construct () {
    const newUser = Reflect.construct(...arguments)
    userList.push(newUser)
    return newUser
  }
})
new proxy('John')
new proxy('Jacob')
new proxy('Jingleheimerschmidt')
console.log(userList) // [User {}, User {}, User{}]

另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:

const userList = []
function emit (newValue) {
  console.log(newValue)
}
const proxy = new Proxy(userList, {
  set (target, property, value, receiver) {
    const result = Reflect.set(...arguments)
    if (result) {
      emit(Reflect.get(target, property, receiver))
    }
    return result
  }
})
proxy.push('John')
// John
proxy.push('Jacob')
// Jacob

六、小结

  • 代理是 ECMAScript 6 新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了一片前所未有的 JavaScript 元编程及抽象的新天地
  • 从宏观上看,代理是真实 JavaScript 对象的透明抽象层。代理可以定义包含捕获器的处理程序对象,而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任何基本操作的行为,当然前提是遵从捕获器不变式
  • 与代理如影随形的反射 API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射 API 看作一套基本操作,这些操作是绝大部分 JavaScript 对象 API 的基础
  • 代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可观察对象
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天内卷一点点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值