抱歉,學會 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 的更多玩法,你們好好挖掘挖掘 👏

相關文章
相關標籤/搜索