1. 代理是目标对象的抽象,目标对象既可以直接操作,也可以通过代理对象操作,但是直接操作会绕过代理施予的行为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 使用 Proxy 创建代理,这个函数接受必须的两个参数,目标对象和处理程序对象
    const target = {
    id: 1
    }
    const handler = {}

    const proxy = new Proxy(target, handler)

    proxy.id = 2
    console.log(target.id) // 2

    target.id = 3
    console.log(proxy.id) // 3

    // Proxy.prototype 是 undefined,所以不能使用 instanceof 操作符
    target instanceof Proxy // Function has non-object prototype 'undefined' in instanceof check
    proxy instanceof Proxy // Function has non-object prototype 'undefined' in instanceof check
  2. 处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,每次在代理对象上调用这些基本操作时,代理都可以在这些操作被传到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为

  3. 所有的捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的行为,但是我们手动的重建被捕获方法的原始行为并不现实,实际开发中我们可以调用 Reflect 对象上的同名方法来完成对捕获行为的原始行为的重建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const target = {
    id: 1
    }
    const handler = {
    get() {
    return Reflect.get(...arguments)
    }
    }

    const proxy = new Proxy(target, handler)
    console.log(proxy.id) // 1
  4. 你也可以基于 Reflect 的样板对捕获的方法进行修饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const target = {
    id: 1
    }
    const handler = {
    get() {
    return Reflect.get(...arguments) + '!!!'
    }
    }

    const proxy = new Proxy(target, handler)
    console.log(proxy.id) // 1!!!
  5. 捕获器几乎可以改变所有基本方法的行为,但是也有一定的限制,比如一个不可配置,不可写的的数据属性在捕获器返回与其属性不同的值时会抛出异常

  6. 代理与目标对象之间的联系时可以断开的,Proxy 暴露了 revocable() 方法,这个方法可以创建一个可撤销的代理对象,需要注意的是,撤销操作是不可逆且幂等的,撤销代理之后再次调用代理会抛出 TypeError

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const target = {
    id: 1
    }
    const handler = {
    get() {
    return Reflect.get(...arguments)
    }
    }

    // 创建可撤销的代理对象
    const { proxy, revoke } = Proxy.revocable(target, handler)

    console.log(proxy.id) // 1

    revoke()

    console.log(proxy.id) // TypeError: Cannot perform 'get' on a proxy that has been revoked
  7. 很多反射 API 方法在对象上都有对象的方法,我们也可以用它来重构对象的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const target = {
    id: 1
    }

    try {
    Object.defineProperty(target, 'name', 'VaynePeng')
    console.log('success')
    } catch (error) {
    console.log('error: ', error) // TypeError: Property description must be an object: VaynePeng
    }

    使用 Reflect 进行重构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const target = {
    id: 1
    }

    if (Reflect.defineProperty(target, 'name', { value: 'VaynePeng' })) {
    console.log('success')
    } else {
    console.log('fail')
    }
  8. 一些常用的反射方法

    • 以下方法都会提供状态标记
      • Reflect.defineProperty()
      • Reflect.deleteProperty()
      • Reflect.preventExtensions()
      • Reflect.setPrototypeOf()
      • Reflect.set()
    • 用一等函数替代对象,以下的反射方法提供了只有通过操作符才能完成的操作
      • Reflect.get() // 可以替代对象属性访问操作符
      • Reflect.set() // 可以替代对象属性赋值 = 操作符
      • Reflect.has() // 可以替代 in 操作符或 with()
      • Reflect.deleteProperty() // 可以替代 delete 操作符
      • Reflect.construct() // 可以替代 new 操作符
  9. 在通过 apply 方法调用函数时,被调用的函数可能定义了自己的 apply 属性,这时我们一般都会使用定义在 Function 原型上的 apply 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    Function.prototype.apply.call(func, context, args)
    func:是要调用的函数
    context:是要指定的函数上下文(即 this 值),也就是在函数内部可用的 this 对象
    args:是一个数组,包含要传递给函数的参数

    const obj = {
    value: 10,
    }

    function method(a, b) {
    return this.value + a + b
    }

    method.apply = function () {
    return '自定义apply'
    }

    method.apply(obj, [1, 2]) // 自定义apply
    Function.prototype.apply.call(method, obj, [1, 2]) // 13

    // 使用 Reflect 重写
    Reflect.apply(method, obj, [1, 2]) // 13
  10. 代理 Date 类型时,访问某些属性可能报错

    1
    2
    3
    4
    5
    const target = new Date()

    const proxy = new Proxy(target, {})

    console.log(proxy.getDate()) // this is not a Date object
  11. 代理捕获器和反射方法

    代理可以捕获13中不同得基本操作,这些操作有各自不同得反射 API 方法

    • get() 捕获器会在获取属性值得操作中被调用。对应的反射 API 方法为 Reflect.get()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target 目标对象
      * @param {string} property 引用的目标对象上的字符串键属性
      * @param {*} receiver 代理对象或继承代理对象的对象
      * @returns 返回值无限制
      */
      get(target, property, receiver) {
      console.log('get()')
      return Reflect.get(...arguments)
      }
      })

      proxy.foo // get()
      • 拦截的操作
        • proxy.property
        • proxy[property]
        • Object.create(proxy)[property]
        • Reflect.get(proxy, property, receiver)
      • 捕获器不变式
        • 如果 target.property 不可写且不可配置,则处理程序返回的值必须与 target.property 匹配。
        • 如果 target.property 不可配置且 [[Get]] 特性为 undefined,处理程序的返回值也必须是 undefined
    • set() 捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target 目标对象
      * @param {string} property 引用的目标对象上的字符串键属性
      * @param {*} value 要赋给属性的值
      * @param {*} receiver 接受最初赋值的对象
      * @returns 返回布尔值,true 为成功,false 为失败,返回非布尔值会被转为布尔值
      */
      set (target, property, value, receiver) {
      console.log('set()')
      return Reflect.set(...arguments)
      }
      })

      proxy.foo = 2 // set()
      • 拦截的操作
        • proxy.property = value
        • proxy[property] = value
        • Object.create(proxy)[property] = value
        • Reflect.set(proxy, property, value, receiver)
      • 捕获器不变式
        • 如果 target.property 不可写且不可配置,则不能修改目标属性的值。
        • 如果 target.property 不可配置且 [[Set]] 特性为 undefined ,则不能修改目标属性的值。
        • 在严格模式下,处理程序返回 false 会抛出 TypeError
    • has() 捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target 目标对象
      * @param {string} property 引用的目标对象上的字符串键属性
      * @returns 必须返回布尔值,表示属性是否存在,返回非布尔值会被转为布尔值
      */
      has(target, property) {
      console.log('has()')
      return Reflect.has(...arguments)
      }
      })

      'foo' in proxy // has()
      • 拦截的操作
        • property in proxy
        • property in Object.create(proxy)
        • with(proxy) { (property) }
        • Reflect.has(proxy, property)
      • 捕获器不变式
        • 如果 target.property 存在且不可配置,则处理程序必须返回 true
        • 如果 target.property 存在且目标对象不可扩展,则处理程序必须返回 true
    • defineProperty() 捕获器会在 Object.defineProperty() 中被调用。对应的反射 API 方法为 Reflect.defineProperty()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @param {string} property // 引用的目标对象上的字符串键属性
      * @param {*} descriptor// 要定义或修改的属性的描述符
      * @returns 必须返回布尔值,表示属性是否成功定义,返回非布尔值会被转为布尔值
      */
      defineProperty (target, property, descriptor) {
      console.log('defineProperty()')
      return Reflect.defineProperty(...arguments)
      }
      })

      Object.defineProperty(proxy, 'bar', { value: 2 }) // defineProperty()
      • 拦截操作
        • Object.defineProperty(proxy, property, descriptor)
        • Reflect.defineProperty(proxy, property, descriptor)
      • 捕获器不变式
        • 如果目标对象不扩展,则无法定义属性
        • 如果目标对象有一个可配置的属性,则不能添加同名的不可配置的属性
        • 如果目标对象有一个不可配置的属性,则不能添加同名的可配置的属性
    • getOwnPropertyDescriptor() 捕获器会在 Object.getOwnPropertyDescriptor() 中被调用,对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @param {string} property // 引用的目标对象上的字符串键属性
      * @returns 必须返回对象,或者在属性不存在时返回 undefined
      */
      getOwnPropertyDescriptor (target, property) {
      console.log('getOwnPropertyDescriptor()')
      return Reflect.getOwnPropertyDescriptor(...arguments)
      }
      })

      Object.getOwnPropertyDescriptor(proxy, 'foo') // getOwnPropertyDescriptor()
      • 拦截操作
        • Object.getOwnPropertyDescriptor(proxy, property)
        • Reflect.getOwnPropertyDescriptor(proxy, property)
      • 捕获器不变式
        • 如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的对象
        • 如果自有的 target.property 存在且可配置,则处理程序必须返回一个表示该属性可配置的对象
        • 如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该对象存在的对象
        • 如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属性不存在
        • 如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象
    • deleteProperty() 捕获器会在 delete 操作符中被调用,对应的反射 API 方法为 Reflect.deleteProperty()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      const target = {
      foo: 1
      }

      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @param {string} property // 引用的目标对象上的字符串键属性
      * @returns 必须返回布尔值,表示删除属性是否成功,返回非布尔值会被转化为布尔值
      */
      deleteProperty (target, property) {
      console.log('deleteProperty()')
      return Reflect.deleteProperty(...arguments)
      }
      })

      delete proxy.foo // deleteProperty()
      • 拦截操作
        • delete proxy.property
        • delete proxy[property]
        • Reflect.deleteProperty(proxy, property)
      • 捕获器不变式
        • 如果自有的 target.property 存在且不可配置,则处理程序不会删除这个属性
    • ownKeys() 捕获器会在 Object.keys() 及类似方法中被调用,对应的反射 API 方法为 Reflect.ownKeys()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = {
      foo: 1
      }
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @returns 必须返回包含字符串或者 Symbol 的可枚举对象
      */
      ownKeys (target) {
      console.log('ownKeys()')
      return Reflect.ownKeys(...arguments)
      }
      })

      Object.keys(proxy) // ownKeys()
      • 拦截操作
        • Object.getOwnPropertyNames(proxy) 返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)
        • Object.getOwnPropertySymbols(proxy) 返回一个包含给定对象所有自有 Symbol 属性的数组
        • Object.keys(proxy) 返回一个由给定对象自身的可枚举的字符串键属性名组成的数组
        • Reflect.ownKeys(proxy) 返回一个由目标对象自身的属性键组成的数组,包含 Symbol 键
      • 捕获器不变式
        • 返回的可枚举对象必须包含 target 的所有不可配置的自有属性,如果 target 不可扩展,则返回可枚举对象必须准确的包含自有属性键
    • getPrototypeOf() 捕获器会在 Object.getPrototypeOf() 中被调用,对应的反射 API 方法为 Reflect.getPrototypeOf()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = {
      foo: 1
      }
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @returns 必须返回对象或者null
      */
      getPrototypeOf (target) {
      console.log('getPrototypeOf()')
      return Reflect.getPrototypeOf(...arguments)
      }
      })

      Object.getPrototypeOf(proxy) // getPrototypeOf()
      • 拦截操作
        • Object.getPrototypeOf(proxy)
        • Reflect.getPrototypeOf(proxy)
        • proxy.__proto__
        • Object.prototype.isPrototypeOf(proxy)
        • proxy instanceof Object
      • 捕获器不变式
        • 如果 target 不可扩展,则 Object.getPrototypeOf(proxy) 唯一有效的返回值就是 Object.getPrototypeOf(proxy) 的返回值
    • setPrototypeOf() 捕获器会在 Object.setPrototypeOf() 中被调用,对应的反射 API 方法为 Reflect.setPrototypeOf()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      const target = {
      foo: 1
      }
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @param {*} prototype // target 的替代原型,如果是顶级原型则为 null
      * @returns 必须返回布尔值,表示原型赋值是否成功,返回非布尔类型会转化为布尔类型
      */
      setPrototypeOf(target, prototype) {
      console.log('setPrototypeOf()')
      return Reflect.setPrototypeOf(...arguments)
      }
      })

      Object.setPrototypeOf(proxy) // setPrototypeOf()
      • 拦截操作
        • Object.setPrototypeOf(proxy)
        • Reflect.setPrototypeOf()
      • 捕获器不变式
        • 如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(proxy) 的返回值
    • isExtensible() 捕获器会在 Object.isExtensible() 中被调用,对应的反射 API 方法为 Reflect.isExtensible()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = {
      foo: 1
      }
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @returns 必须返回布尔值,表示 target 是否可扩展,返回非布尔类型会转化为布尔类型
      */
      isExtensible(target) {
      console.log('isExtensible()')
      return Reflect.isExtensible(...arguments)
      }
      })

      Object.isExtensible(proxy) // isExtensible()
      • 拦截操作
        • Object.isExtensible(proxy)
        • Reflect.isExtensible(proxy)
      • 捕获器不变式
        • 如果 target 可扩展,则值处理程序必须返回 true
        • 如果 target 不可扩展,则值处理程序必须返回 false
    • preventExtensions() 捕获器会在 Object.preventExtensions() 中被调用,对应的反射 API 方法为 Reflect.preventExtensions()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = {
      foo: 1
      }
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @returns 必须返回布尔值,表示 target 是否已经不可扩展,返回非布尔类型会转化为布尔类型
      */
      preventExtensions(target) {
      console.log('preventExtensions()')
      return Reflect.preventExtensions(...arguments)
      }
      })

      Object.preventExtensions(proxy) // preventExtensions()
      • 拦截操作
        • Object.preventExtensions(proxy) // 将对象变得不可扩展
        • Reflect.preventExtensions(proxy)
      • 捕获器不变式
        • 如果 Object.isExtensible(proxy) 是 false,则处理程序必须返回 true
    • apply() 捕获器会在调用函数时被调用,对应的反射 API 方法为 Reflect.apply()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = () => {}
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标对象
      * @param {*} thisArg// 调用函数时的 this 参数
      * @param {*} argumentsList// 调用函数时的参数列表
      * @returns 无限制
      */
      apply(target, thisArg, ...argumentsList) {
      console.log('apply()')
      return Reflect.apply(...arguments)
      }
      })

      proxy() // apply()
      • 拦截操作
        • proxy()
        • Funtion.prototype.apply(thisArg, argumentsList)
        • Funtion.prototype.call(thisArg, ...argumentsList)
        • Reflect.apply(target, thisArg, argumentsList)
      • 捕获器不变式
        • target 必须是一个函数对象
    • construct() 捕获器会在 new 操作符中被调用,对应的反射 API 方法为 Reflect.construct()

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const target = function() {}
      const proxy = new Proxy(target, {
      /**
      * @param {*} target // 目标构造函数
      * @param {*} argumentsList // 传给构造函数的参数
      * @param {*} newTarget// 最初被调用的构造函数这里指的是 proxy
      * @returns 必须返回一个对象
      */
      construct(target, argumentsList, newTarget) {
      console.log('construct()')
      return Reflect.construct(...arguments)
      }
      })

      new proxy() // construct()
      • 拦截操作
        • new proxy()
        • Reflect.construct(target, argumentsList, newTarget)
      • 捕获器不变式
        • target 必须可以用作构造函数