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 的更多玩法,你们好好挖掘挖掘 👏