Proxy— это новая функция JavaScript 2015, давайте посмотрим, какие интересные вещи он реализует.
Более безопасные типы перечисления
В JavaScript мы обычно используем объект для представления значения перечисления.
Но это часто небезопасно, мы хотим перечислить значения:
- Если его нет, сообщите об ошибке.
- Динамические настройки запрещены, иначе будет выдано сообщение об ошибке.
- Удаление не допускается, иначе будет сообщено об ошибке.
Мы напишемenum
, но сначала давайте взглянем на его применение типов действий в redux.
// 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
Как написать функцию?
Очень просто, просто используйте проксиget
, set
а такжеdeleteProperty
Крючок в порядке.
// 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')
}
})
}
Чтобы расширить, можем ли мы написатьбиблиотека проверки типов, мы не будем здесь распространяться.
тест, издевательство
использоватьapply
Hook, Proxy может обнаружить вызов функции.
Вот простой для модульного тестированияшпионская библиотека. Он может получить количество вызовов функции, а также параметры вызова.
// 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])
Кроме того, сПрокси пишет библиотеку утвержденийЭто также очень удобно, поэтому я не буду здесь его распространять.
Immutable
Мы также можем использовать Proxy для выполнения некоторых операций со структурами данных, таких как реализацияimmerНеизменяемая библиотека.
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 保持不变
})
Обратите внимание, реактивная система
Использование прокси для реализации шаблона 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-подобныйОтзывчивая система.
еще интересные примеры
Мы также можем использовать прокси для достижения многих целей, таких как точки захоронения и мониторинг производительности?
Больше способов играть в прокси, давайте копать и копать 👏