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
, set
和 deleteProperty
鉤子就行了。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
利用 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
咱們也能夠利用 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 保持不變
})
複製代碼
用 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 的更多玩法,你們好好挖掘挖掘 👏