A practical guide to Javascript Proxy

1. The Default/” Zero Values”

Provide zero values for objects (including arrays). Zero values are Boolean(false), Number(0), String(“), Object({}), Array([]), etc.

const withZeroValue = (target, zeroValue) = > new Proxy(target, {
  get: (obj, prop) = > (prop in obj) ? obj[prop] : zeroValue
})

let pos = {
  x: 4.y: 19
}

console.log(pos.x, pos.y, pos.z) // 4, 19, undefined

pos = withZeroValue(pos, 0)

console.log(pos.x, pos.y, pos.z) / / 4, 19, 0
Copy the code

2. Negative Array Indices

Fetching the last element of the array in JS normally uses arr[arr.length-1], which is repeated and may have an error of one bit.

const negativeArray = (els) = > new Proxy(els, {
  get: (target, propKey, receiver) = > Reflect.get(target,
    (+propKey < 0)?String(target.length + +propKey) : propKey, receiver)
});

const unicorn = negativeArray(['🐴'.'🎂'.'🌈']);

unicorn[-1] / / '🌈'
Copy the code

Related quotes:

Tc39array. lastItem proposal: proposal-array-last.

Negative Array Indices for Ruby: Ruby Quicktips

NPM package: negative – array

3. Hiding Properties

Private attributes are often preceded by _, and the hide function makes attributes prefixed with _ non-iterable.

const hide = (target, prefix = '_') = > new Proxy(target, {
  has: (obj, prop) = >(! prop.startsWith(prefix) && propin obj),
  ownKeys: (obj) = > Reflect.ownKeys(obj)
    .filter(prop= > (typeofprop ! = ="string"| |! prop.startsWith(prefix))),get: (obj, prop, rec) = > (prop in rec) ? obj[prop] : undefined
})

let userData = hide({
  firstName: 'Tom'.mediumHandle: '@tbarrasso'._favoriteRapper: 'Drake'
})

userData._favoriteRapper        // undefined
('_favoriteRapper' in userData) // false
Object.keys(userData)           // ['firstName', 'mediumHandle']
Copy the code

You can use Object.defineProperty to set the property or Symbol value to the property to make it non-iterative.

4. Caching

There are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.

The ephemeral function sets how long a property will time out. The value of this property cannot be obtained on timeout (cache invalidation).

const ephemeral = (target, ttl = 60) = > {
  const CREATED_AT = Date.now()
  const isExpired = () = > (Date.now() - CREATED_AT) > (ttl * 1000)
  
  return new Proxy(target, {
    get: (obj, prop) = > isExpired() ? undefined : Reflect.get(obj, prop)
  })
}

let bankAccount = ephemeral({
  balance: 14.93
}, 10)

console.log(bankAccount.balance)    / / 14.93

setTimeout(() = > {
  console.log(bankAccount.balance)  // undefined
}, 10 * 1000)
Copy the code

5. Enums & Read-Only Views

Enumeration (enum) returns an error when retrieving a nonexistent property, which is similar to the default value of the first property. The default value is returned and the error is reported, but its behavior is similar to the enumeration type so it is listed separately.

const createEnum = (target) = > readOnlyView(new Proxy(target, {
  get: (obj, prop) = > {
    if (prop in obj) {
      return Reflect.get(obj, prop)
    }
    throw new ReferenceError(`Unknown prop "${prop}"`)}}))Copy the code

A read-only object

const NOPE = () = > {
  throw new Error("Can't modify read-only view");
}

const NOPE_HANDLER = {
  set: NOPE,
  defineProperty: NOPE,
  deleteProperty: NOPE,
  preventExtensions: NOPE,
  setPrototypeOf: NOPE
}

const readOnlyView = target= >
  new Proxy(target, NOPE_HANDLER)

let SHIRT_SIZES = createEnum({
  S: 10.M: 15.L: 20
})

SHIRT_SIZES.S / / 10
SHIRT_SIZES.S = 15

// Uncaught Error: Can't modify read-only view

SHIRT_SIZES.XL

// Uncaught ReferenceError: Unknown prop "XL"
Copy the code

A link to the

TS enum type

6. Operator Overload

Override the IN operator. It feels like a bit of a mental load, but it probably adds to the readability of the code.

const range = (min, max) = > new Proxy(Object.create(null), {
  has: (_, prop) = > (+prop >= min && +prop <= max)
})

const X = 10.5
const nums = [1.5, X, 50.100]

if (X in range(1.100)) { // true
  // ...
}

nums.filter(n= > n in range(1.10)) / / [1, 5]
Copy the code

Not only can you calculate the range of numbers, you can also exclude range numbers, natural numbers, rational numbers, imaginary numbers, infinite numbers and so on.

You can also override delete, new.

7. Cookies Object

Document. Cookie obtains a string, which can be easily implemented as an object of cookies for convenient operation.

_octo = GH1.2.2591.47507; _ga = GA1.1.62208.4087; has_recent_activity=1Copy the code
const getCookieObject = () = > {
    const cookies = document.cookie.split('; ').reduce((cks, ck) = > 
	({[ck.substr(0, ck.indexOf('=')).trim()]: ck.substr(ck.indexOf('=') + 1), ...cks}), {});
    const setCookie = (name, val) = > document.cookie = `${name}=${val}`;
    const deleteCookie = (name) = > document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; `;

    return new Proxy(cookies, {
		set: (obj, prop, val) = > (setCookie(prop, val), Reflect.set(obj, prop, val)),
        deleteProperty: (obj, prop) = > (deleteCookie(prop), Reflect.deleteProperty(obj, prop))
     })
}


let docCookies = getCookieObject()

docCookies.has_recent_activity              / / "1"
docCookies.has_recent_activity = "2"        / / "2"
delete docCookies["has_recent_activity"]   // true

Copy the code

So as you can imagine, strings like key-value pairs can be wrapped in a Proxy to facilitate subsequent operations.

conclusion

Why Proxy?

My personal understanding is:

  1. Improve the semantics of the code, changing the way the original object operates on all aspects of the property, rather than wrapping it with extra functions and so on. The wrapped object naturally has special operations on the set property.

  2. Objects can be enhanced with simple configurations, such as making cookies read only, hiding special attributes, and with ZeroValue:

    / / the document. The cookie = "_octo = GH1.2.2591.47507; _ga = GA1.1.62208.4087; has_recent_activity=1"
    
    let docCookies = withZeroValue(hide(readOnlyView(getCookieObject())), "Cookie not found")
    
    docCookies.has_recent_activity  / / "1"
    docCookies.nonExistentCookie    // "Cookie not found"
    docCookies._ga                  // "Cookie not found"
    docCookies.newCookie = "1"      // Uncaught Error: Can't modify read-only view
    Copy the code

Sum up the role of Proxy in one sentence?

It may not be a complete summary, but the most important function is to enhance objects.