Skip to content

proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义

语法参数

js
const p = new Proxy(target, handler)
const p = new Proxy(target, handler)
  • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
handler.getPrototypeOf()
handler.setPrototypeOf()
handler.isExtensible()
handler.getOwnPropertyDescriptor()
handler.preventExtensions()
handler.defineProperty()
handler.has()
handler.get()
handler.set()
handler.deleteProperty()
handler.ownKeys()
handler.apply()
handler.construct()
handler.getPrototypeOf()
handler.setPrototypeOf()
handler.isExtensible()
handler.getOwnPropertyDescriptor()
handler.preventExtensions()
handler.defineProperty()
handler.has()
handler.get()
handler.set()
handler.deleteProperty()
handler.ownKeys()
handler.apply()
handler.construct()

p 可以通过 handler 扩展以上Object原生方法

通过代理验证数据

js
const validator = {
  set(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value))
        throw new TypeError('The age is not an integer')

      if (value > 200)
        throw new RangeError('The age seems invalid')

    }

    // The default behavior to store the value
    obj[prop] = value

    // 表示成功
    return true
  }
}

const person = new Proxy({}, validator)
person.age = 100
console.log(person.age)
// 100
person.age = 'young'
// 抛出异常:Uncaught TypeError: The age is not an integer
person.age = 300
// 抛出异常:Uncaught RangeError: The age seems invalid
const validator = {
  set(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value))
        throw new TypeError('The age is not an integer')

      if (value > 200)
        throw new RangeError('The age seems invalid')

    }

    // The default behavior to store the value
    obj[prop] = value

    // 表示成功
    return true
  }
}

const person = new Proxy({}, validator)
person.age = 100
console.log(person.age)
// 100
person.age = 'young'
// 抛出异常:Uncaught TypeError: The age is not an integer
person.age = 300
// 抛出异常:Uncaught RangeError: The age seems invalid

修正及附加属性

js
const products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
}, {
  get(obj, prop) {
    // 附加一个属性
    if (prop === 'latestBrowser')
      return obj.browsers[obj.browsers.length - 1]

    // 默认行为是返回属性值
    return obj[prop]
  },
  set(obj, prop, value) {
    // 附加属性
    if (prop === 'latestBrowser') {
      obj.browsers.push(value)
      return
    }
    // 如果不是数组,则进行转换
    if (typeof value === 'string')
      value = [value]

    // 默认行为是保存属性值
    obj[prop] = value
    // 表示成功
    return true
  }
})

console.log(products.browsers) // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox' // 如果不小心传入了一个字符串
console.log(products.browsers) // ['Firefox'] <- 也没问题,得到的依旧是一个数组

products.latestBrowser = 'Chrome'
console.log(products.browsers) // ['Firefox', 'Chrome']
console.log(products.latestBrowser) // 'Chrome'
const products = new Proxy({
  browsers: ['Internet Explorer', 'Netscape']
}, {
  get(obj, prop) {
    // 附加一个属性
    if (prop === 'latestBrowser')
      return obj.browsers[obj.browsers.length - 1]

    // 默认行为是返回属性值
    return obj[prop]
  },
  set(obj, prop, value) {
    // 附加属性
    if (prop === 'latestBrowser') {
      obj.browsers.push(value)
      return
    }
    // 如果不是数组,则进行转换
    if (typeof value === 'string')
      value = [value]

    // 默认行为是保存属性值
    obj[prop] = value
    // 表示成功
    return true
  }
})

console.log(products.browsers) // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox' // 如果不小心传入了一个字符串
console.log(products.browsers) // ['Firefox'] <- 也没问题,得到的依旧是一个数组

products.latestBrowser = 'Chrome'
console.log(products.browsers) // ['Firefox', 'Chrome']
console.log(products.latestBrowser) // 'Chrome'

通过属性查找数组中特点的对象

js
const products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
], {
  get(obj, prop) {
    // 默认行为是返回属性值,prop ?通常是一个整数
    if (prop in obj)
      return obj[prop]

    // 获取 products 的 number; 它是 products.length 的别名
    if (prop === 'number')
      return obj.length

    let result; const types = {}

    for (const product of obj) {
      if (product.name === prop)
        result = product

      if (types[product.type])
        types[product.type].push(product)
      else
        types[product.type] = [product]

    }
    // 通过 name 获取 product
    if (result)
      return result

    // 通过 type 获取 products
    if (prop in types)
      return types[prop]

    // 获取 product type
    if (prop === 'types')
      return Object.keys(types)

    return undefined
  }
})

console.log(products[0]) // { name: 'Firefox', type: 'browser' }
console.log(products.Firefox) // { name: 'Firefox', type: 'browser' }
console.log(products.Chrome) // undefined
console.log(products.browser) // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types) // ['browser', 'mailer']
console.log(products.number) // 3
const products = new Proxy([
  { name: 'Firefox', type: 'browser' },
  { name: 'SeaMonkey', type: 'browser' },
  { name: 'Thunderbird', type: 'mailer' }
], {
  get(obj, prop) {
    // 默认行为是返回属性值,prop ?通常是一个整数
    if (prop in obj)
      return obj[prop]

    // 获取 products 的 number; 它是 products.length 的别名
    if (prop === 'number')
      return obj.length

    let result; const types = {}

    for (const product of obj) {
      if (product.name === prop)
        result = product

      if (types[product.type])
        types[product.type].push(product)
      else
        types[product.type] = [product]

    }
    // 通过 name 获取 product
    if (result)
      return result

    // 通过 type 获取 products
    if (prop in types)
      return types[prop]

    // 获取 product type
    if (prop === 'types')
      return Object.keys(types)

    return undefined
  }
})

console.log(products[0]) // { name: 'Firefox', type: 'browser' }
console.log(products.Firefox) // { name: 'Firefox', type: 'browser' }
console.log(products.Chrome) // undefined
console.log(products.browser) // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types) // ['browser', 'mailer']
console.log(products.number) // 3

定义私有属性

js
const target = {
  _id: '1024',
  name: 'vuejs'
}

const proxy = new Proxy(target, {
  get(target, propkey, proxy) {
    if (propkey[0] === '_')
      throw new Error(`${propkey} is restricted`)

    return Reflect.get(target, propkey, proxy)
  },
  set(target, propkey, value, proxy) {
    if (propkey[0] === '_')
      throw new Error(`${propkey} is restricted`)

    return Reflect.set(target, propkey, value, proxy)
  }
})

proxy.name // vuejs
proxy._id // Uncaught Error: _id is restricted
proxy._id = '1025' // Uncaught Error: _id is restricted
const target = {
  _id: '1024',
  name: 'vuejs'
}

const proxy = new Proxy(target, {
  get(target, propkey, proxy) {
    if (propkey[0] === '_')
      throw new Error(`${propkey} is restricted`)

    return Reflect.get(target, propkey, proxy)
  },
  set(target, propkey, value, proxy) {
    if (propkey[0] === '_')
      throw new Error(`${propkey} is restricted`)

    return Reflect.set(target, propkey, value, proxy)
  }
})

proxy.name // vuejs
proxy._id // Uncaught Error: _id is restricted
proxy._id = '1025' // Uncaught Error: _id is restricted

Proxy.revocable

唯一的静态方法

js
Proxy.revocable(target, handler)
Proxy.revocable(target, handler)

返回一个包含了代理对象本身和它的撤销方法的可撤销 Proxy 对象 {"proxy": proxy, "revoke": revoke}

js
const revocable = Proxy.revocable({}, {
  get(target, name) {
    return `[[${name}]]`
  }
})
const proxy = revocable.proxy
proxy.foo // "[[foo]]"
revocable.revoke()
console.log(proxy.foo) // 抛出 TypeError
const revocable = Proxy.revocable({}, {
  get(target, name) {
    return `[[${name}]]`
  }
})
const proxy = revocable.proxy
proxy.foo // "[[foo]]"
revocable.revoke()
console.log(proxy.foo) // 抛出 TypeError