抱歉,学会 Proxy 真的能够随心所欲

Proxy 是 JavaScript 2015 的一个新特性,下面让咱们看看他实现哪些有趣的东西。javascript

更安全的枚举类型

在 JavaScript 里,咱们一般用一个对象来表示枚举值。html

但这每每是不安全,咱们但愿枚举值:vue

  • 若是不存在的话,报错。
  • 不容许动态设置,不然报错。
  • 不容许删除,不然报错。

咱们下面会写一个 enum 的函数,不过先让咱们来看看他在 redux 的 action types 的应用。java

// enum.test.js
test('enum', () => {
  // 咱们定义了俩个 action type
  const actionTypes = {
    ADD_TODO: 'add_todo',
    UPDATE_TODO: 'update_todo'
  }

  const safeActionTypes = enum(actionTypes)

  // 当读取一个不存在的枚举值时会报错
  // 由于 'DELETE_TODO' 并无定义,因此此时会报错
  expect(() => {
    safeActionTypes['DELETE_TODO']
  }).toThrowErrorMatchingSnapshot()

  // 当删除一个枚举值时会报错
  expect(() => {
    delete safeActionTypes['ADD_TODO']
  }).toThrowErrorMatchingSnapshot()
})
复制代码

那么,enum 函数怎么写呢?
很简单,只要用 Proxy 的 get , setdeleteProperty 钩子就行了。react

// erum.js
export default function enum(object) {
  return new Proxy(object, {
    get(target, prop) {
      if (target[prop]) {
        return Reflect.get(target, prop)
      } else {
        throw new ReferenceError(`Unknown enum '${prop}'`)
      }
    },

    set() {
      throw new TypeError('Enum is readonly')
    },

    deleteProperty() {
      throw new TypeError('Enum is readonly')
    }
  })
}
复制代码

拓展一下的话,咱们是否是能够写个类型校验库,在这里咱们就不展开了。git

测试,Mock

利用 apply 钩子,Proxy 能够检测一个函数的调用状况。es6

下面是一个简单的,用于单元测试的 spy 库。他能够获取函数的调用次数,以及调用时的参数等。github

// spy.js
export function spy() {
  const spyFn = function() {}
  spyFn.toBeCalledTimes = 0
  spyFn.lastCalledWith = undefined

  return new Proxy(spyFn, {
    apply(target, thisArg, argumentsList) {
      target.toBeCalledTimes += 1
      target.lastCalledWith = argumentsList.join(', ')
    }
  })
}

// spy.test.js
const colors = ['red', 'blue']
const callback = spy()

colors.forEach(color => callback(color))

expect(callback.toBeCalledTimes).toBe(colors.length)
expect(callback.lastCalledWith).toBe(colors[1])
复制代码

另外,用 Proxy 写一个断言库也是挺方便的,这里就不展开了。redux

Immutable

咱们也能够利用 Proxy 在数据结构上作些操做,好比实现一个像 immer 的 Immutable 库。数组

import { shallowCopy } from './utils/index'

export function produce(base, producer) {
  const state = {
    base, // 原来的数据
    copy: null, // 新的,复制的数据
    modified: false, // 是否修改过
  }

  const proxy = new Proxy(state, {
    get(target, prop) {
      // 若是修改过,则返回副本数据,或者返回原来的数据
      return target.modified ? target.copy[prop] : target.base[prop]
    },

    set(target, prop, value) {
      // set 钩子的时候,设置 modifyied 为 true
      if (!target.modifyied) {
        target.modified = true
        target.copy = shallowCopy(target.base)
      }

      target.copy[prop] = value
      return true
    }
  })

  producer.call(proxy, proxy)

  return proxy
}
复制代码

实际效果就像下面这个样子:

咱们获得了新的不一样的 nextState ,可是原来的 baseState 并无发生变化。

test('produce', () => {
  const baseState = {
    name: 'foo'
  }
  const nextState = produce(baseState, draft => {
    draft.name = 'bar'
  })

  expect(nextState.name).toBe('bar') // nestState 发生了变化
  expect(baseState.name).toBe('foo') // 而 baseState 保持不变
})
复制代码

Observe,响应式系统

用 Proxy 来实现一个 pub/sub 模式也是挺简单的。

// observe.js
export function observe(target, onChange) {
  return createProxy(target, onChange)
}

function createProxy(target, onChange) {
  const trap = {
    get(object, prop) {
      const value = object[prop]

      // 这里能够优化一下,不该该每次都建立新的 proxy
      if (typeof value === 'object' && value !== null) {
        return createProxy(object[prop], onChange)
      }

      return value
    },

    set(object, prop, value, ...args) {
      onChange()
      return Reflect.set(object, prop, value, ...args)
    }
  }

  return new Proxy(target, trap)
}

// observe.test.js
test('observe', () => {
  const stub = jest.fn()
  const data = {
    user: {
      name: 'foo',
    },
    colors: ['red'],
  }

  const reactiveData = observe(data, stub)

  // push 会触发两次 set 钩子
  // 第一次把 colors 的 2 属性设置为 'blue'
  // 第二次把 colors 的 length 属性设置为 2
  reactiveData.colors.push('blue')

  reactiveData.user.name = 'baz'

  // 动态增长一个新的属性
  reactiveData.type = 'zzz'

  expect(stub).toHaveBeenCalledTimes(4)
})
复制代码

从上面能够发现,Proxy 不只能够代理对象,也能够代理数组;还能够代理动态增长的属性如 type 。这也是 Object.defineProperty 作不到的。

加个依赖追踪的话,咱们就能够实现一个相似 Vue 或者 Mobx 的响应式系统了。

更多有趣的例子

咱们还能够用 Proxy 实现不少东西,好比埋点能够不,性能监控能够不?

Proxy 的更多玩法,你们好好挖掘挖掘 👏

相关文章
相关标签/搜索