ES6中的Proxy学习心得

370 阅读3分钟

Proxy

一、什么是代理?

代理(Proxy)是我们通过代理控制对另一个对象的访问。通过代理可以定义当对象发生交互时可执行的自定义行为——如读取或设置属性值,或调用方法。可以将代理理解为通用化的settergetter,区别是每个settergetter仅能控制单个对象属性,而代理可用于对象交互的通用处理,包括调用对象的方法。

使用代理,我们可以很容易地在代码中添加分析和性能度量;自动填充对象属性以避免讨厌的null异常;包装宿主对象,例如DOM用于减少跨浏览器的不兼容性。

二、怎么创建代理(Proxy)?

通过Proxy的构造器来创建一个代理,像下面这样:

let obj = { name: 'Jack' }
let proxy = new Proxy(obj, {})

上面的代码就创建了一个代理对象proxyProxy构造器接收两个参数,

  • 第一个参数表示要代理哪个对象,
  • 第二个参数是一个对象,表示的是这个代理能够做什么,比如校验,日志记录等等

那么创建好代理对象之后,能做什么呢?下面就来看看Proxy的应用。

三、代理(Proxy)的应用

3.1、使用代理记录日志(代理对象)

代理的直接用途之一是在我们读写属性时使用一种更好的、更清洁的方式启用日志记录。

使用代理易于在对象上添加日志,看下面的代码:

function makeLogger(target) {
  // 定义形参为 target 的函数,并使得 target 可以记录日志
  return new Proxy(target, {
    // 针对 target 对象创建代理
    get: (target, key) => {
      report('Reading ' + key) // 通过 get 方法实现属性读取时的记录日志
      return target[key]
    },
    set: (target, key, value) => {
      report('Writing value ' + value + ' to ' + key) // 通过 set 方法实现属性赋值时的记录日志
      target[key] = value
    },
  })
}

let obj = { name: 'carson' }
obj = makeLogger(obj) // 把 obj 作为目标对象传入 makeLogger 方法,使其可以记录日志

定义makeLogger函数,使用target对象作为形参,返回一个新的代理对象,该代理对象具有getset方法。getset方法会在读取对象属性时记录日志。

3.2、使用代理检测性能(代理函数)

先看下面的代码:

function isPrime(num) {
  if (num < 2) {
    return false
  }
  for (let i = 2; i < num; i++) {
    if (num % i === 0) {
      return false
    }
  }
  return true
}

isPrime = new Proxy(isPrime, {
  // 使用代理包装 isPrime 方法
  apply: (target, thisArgs, args) => {
    // 定义 apply 方法,当代理对象作为函数调用时将会触发该 apply 方法的执行
    console.log(target, thisArgs, args)
    console.time('isPrime')
    const result = target.apply(thisArgs, args) // 调用目标函数
    console.timeEnd('isPrime')
    return result
  },
})

isPrime(1299827) // 同调用原始方法一样,调用 isPrime 方法

使用isPrime函数作为代理的目标对象。同时,添加apply方法,当调用isPrime函数时就会调用apply方法。

3.3、使用代理实现负数组索引(代理数组)

JavaScript不支持数组负索引,但是,我们可以使用代理进行模拟,看下面的代码:

const array = ['jack', 'tom', 'dev']

function createArrayProxy(array) {
  if (!Array.isArray(array)) {
    throw new TypeError('array is not a array')
  }
  return new Proxy(array, {
    // 返回新的代理,该代理使用传入的数组作为代理目标
    get: (target, index) => {
      // 当读取数组元素时调用 get 方法
      index = +index
      // 如果访问的是负向索引,则逆向访问数组,如果访问的是正向索引,则正常访问数组
      return target[index < 0 ? target.length + index : index]
    },
    set: (target, index, value) => {
      // 当写入数组元素时调用 get 方法
      index = +index
      target[index < 0 ? target.length + index : index] = val
    },
  })
}

const arrayProxy = createArrayProxy(array)
arrayProxy[-1]

创建并返回代理,该代理具有读取数组元素时将被调用的get方法,以及写入数组元素时被调用的set方法。

四、总结

使用代理可以优雅地实现以下内容。

  • 日志记录。
  • 性能测量。
  • 数据校验。
  • 自动填充对象属性(以此避免讨厌的null异常)。
  • 数组负索引。

但是,代理效率不高,所以在需要执行多次的代码中需要谨慎使用。建议进行性能测试。