Proxy is a new feature in JavaScript 2015, so let’s take a look at some of the interesting things it implements.

More secure enumerated types

In JavaScript, we usually represent enumerated values with an object.

But this is often unsafe and we want to enumerate values:

  • If not, an error is reported.
  • Dynamic setting is not allowed; otherwise, an error will be reported.
  • Delete is not allowed, otherwise an error is reported.

We’ll write an enum function next, but first let’s look at its application to Redux’s Action Types.

// we define two action types const actionTypes = {ADD_TODO: 'ADD_TODO ', UPDATE_TODO: 'update_todo'} const safeActionTypes = enum(actionTypes) // An error will be reported when reading a nonexistent enumeration value because 'DELETE_TODO' is not defined, So at this point, an expect (() = > {safeActionTypes [' DELETE_TODO]}). ToThrowErrorMatchingSnapshot () / / an error when deleting an enumeration values expect (() = > { delete safeActionTypes['ADD_TODO'] }).toThrowErrorMatchingSnapshot() })Copy the code

So how do I write an enum function? It’s as simple as using Proxy get, set, and deleteProperty hooks.

// 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')
    }
  })
}
Copy the code

So if we were to expand, maybe we could write a type check library, which we’re not going to expand here.

Test, the Mock

With the Apply hook, the Proxy can detect a function call.

Here is a simple spy library for unit testing. It can obtain the number of times a function is called, and the parameters of the call, etc.

// 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])Copy the code

In addition, it is quite convenient to write an assertion library using Proxy, which I won’t expand here.

Immutable

We can also use proxies to manipulate data structures, such as implementing an Immutable library like IMmer.

import { shallowCopy } from './utils/index' export function produce(base, producer) { const state = { base, // The original data copy: null, // the new, copied data modified: Const proxy = new proxy (state, {get(target, prop) {const proxy = new proxy (state, {get(target, prop) { Or return the original data return target.modified? Target. copy[prop] : target.base[prop]}, set(target, prop, value) {// Set hook when modifyIED to true if (! target.modifyied) { target.modified = true target.copy = shallowCopy(target.base) } target.copy[prop] = value return true } }) producer.call(proxy, proxy) return proxy }Copy the code

The actual effect would look something like this:

We get a new and different nextState, but the original baseState remains the same.

test('produce', () => { const baseState = { name: 'foo' } const nextState = produce(baseState, Draft => {draft.name = 'bar'}) expect(nextstate.name).tobe ('bar') // nestState has changed Expect (basestate.name).tobe ('foo') // While baseState stays the same})Copy the code

Observe, a responsive system

Implementing a PUB /sub pattern with a Proxy is quite simple.

// observe.js export function observe(target, onChange) { return createProxy(target, onChange) } function createProxy(target, onChange) { const trap = { get(object, Prop) {const value = object[prop] // 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 triggers the set hook twice // The first time the 2 attribute of colors is set to 'blue' // the second time the length attribute of colors is set to 2 Push ('blue') reactiveData.user.name = 'baz' // Dynamically add a new attribute reactiveData.type = 'ZZZ' expect(stub).toHaveBeenCalledTimes(4) })Copy the code

As you can see from the above, Proxy can not only Proxy objects, but also can Proxy arrays; You can also delegate dynamically added properties such as type. This is something object.defineProperty cannot do.

With dependency tracking, we could implement a responsive system like Vue or Mobx.

More interesting examples

There are a lot of things we can do with proxies, like burying points, performance monitoring?

  • How to use JavaScript Proxies for Fun and Profit
  • ES6 Features – 10 Use Cases for Proxy
  • proxy-fun

Proxy more gameplay, we dig dig 👏