Metaprogramming is a powerful technique that allows you to write programs that can create other programs. ES6 makes it easier to take advantage of metaprogramming in JavaScript with proxies and many similar features. ES6 Proxies help redefine the basic operations of objects, thus opening the door to possibilities.

This guide can help you understand why ES6 agents are so good, especially for metaprogramming:

  • What is an ES6 proxy
  • How and when to implement agency
  • How do I perform access control, data binding, and caching using ES6 proxies
  • ES6 agents are not ideal for performance-intensive tasks

Prerequisites and results

This tutorial is aimed at developers with JavaScript experience who are at least familiar with the concept of ES6 proxies. If you already have a solid understanding of agents as a design pattern, this knowledge should translate to reality.

After reading this guide, you should be able to:

  • Know what an ES6 proxy is, how to implement it, and when to use it
  • Use ES6 proxies for access control, caching, and data binding

ES6 Agent Profiling: Targets, Handlers, and Traps

Basically, agency is when something or someone becomes a substitute for something else, so whatever it is, it has to be substituted for a real transaction. ES6 agents work the same way.

In order to implement and use ES6 agents effectively, you must understand three key terms:

  1. Target – the real transaction that an agent replaces, and the Target is what stands behind the agent. This could be any object.
  2. Handler – An object that contains trap logic for all agents.
  3. Traps – Similar to traps in operating systems, traps in this context are a means of providing access to objects in some way (understood as intercepting behavior).

To sum up, here is the simplest implementation that can return different things if an ES6 proxy does not have a given property in the object.

const target = { someProp: 1 } const handler = { get: function(target, key) { return key in target ? target[key] : 'Doesn't exist! '; } } const proxy = new Proxy(target, handler); console.log(proxy.someProp) // 1 console.log(proxy.someOtherProp) // Doesn't exist!Copy the code

ES6 proxies are a powerful feature that facilitates the virtualization of objects in JavaScript.

Data binding: Synchronize multiple objects

Data binding is often difficult to implement due to its complexity. The use of ES6 proxies for bidirectional data binding can be seen in JavaScript’s MVC libraries, where objects are modified when the DOM changes.

In short, data binding is a technique for binding multiple data sources together so that they can be synchronized.

Suppose there is a named username.

<input type="text" id="username" /> 
Copy the code

Suppose you want to synchronize the value of this input with the properties of the object.

const inputState = {
  id: 'username'.value: ' '
}
Copy the code

When the input value changes, it is fairly easy to modify inputState by listening for the input change event and then updating the value of inputState. On the other hand, updating the input when inputState is modified is quite difficult.

ES6 agents can help in this situation.

const input = document.querySelector('#username')
const handler = {
  set: function(target, key, value) {
    if (target.id && key === 'username') {
      target[key] = value;
      document.querySelector(` #${target.id}`)
        .value = value;
      return true
    }
    return false}}const proxy = new Proxy(inputState, handler)
proxy.value = 'John Doe'
console.log(proxy.value, input.value) 
// Both sides will have "John Doe" printed
Copy the code

This way, when inputState changes, the input will reflect the changes that have been made. Combined with listening for the change event, this generates a simple two-way data binding of input and inputState.

While this is a valid use case, it is generally not recommended. More on that later.

Caching: Improves code performance

Caching is an ancient concept that allows very complex and large applications to maintain relative performance. Caching is the process of storing some data so that it can be made available more quickly when requested. The cache does not store any data permanently. Cache invalidation is the process of keeping the cache fresh. This is a common affliction among developers. As Phil Karlton says, “There are only two hard things in computer science: cache invalidation and naming things.”

ES6 proxies make caching easier. For example, if you are checking to see if something exists in an object, it will first check the cache and return data, or do something else to get that data if it doesn’t.

Let’s say you need to make a lot of API calls to get specific information and process it.

const getScoreboad = (player) = > {
  fetch('some-api-url')
    .then((scoreboard) = > {
    // What to do with the scoreboard})}Copy the code

This means that every time a player’s scoreboard is needed, a new call must be made. Instead, you can cache the scoreboard on the first request, and subsequent requests can be retrieved from the cache.

const cache = { 
  'John': ['55'.'99']}const handler = { 
  get: function(target, player) {
    if(target[player] {
       return target[player]
  } else {
    fetch('some-api-url')
      .then(scoreboard= > {
        target[player] = scoreboard
        return scoreboard
      })
    }
  }
}
const proxy = new Proxy(cache, handler)
// Access the cache and do something with the scoreboard
Copy the code

This way, API calls are made only if the cache does not contain the player’s scoreboard.

Access control: Controls what goes in and out of objects

The simplest use case is access control, and most of the ES6 proxy is access control.

Let’s explore some practical applications of access control using E6 proxies.

1. Verify

One of the most intuitive use cases for ES6 proxies is to validate the contents inside an object to ensure that the data in the object is as accurate as possible. For example, if you want to enforce a maximum number of characters for a product description, you can do so.

const productDescs = {}
const handler = {
  set: function(target, key, value) {
    if(value.length > 150) {
      value = value.substring(0.150)
    }
    target[key] = value
  }
}
const proxy = new Proxy(productDescs, handler)
Copy the code

Now, even if you add a description that is more than 150 characters long, it will be deleted and added.

2. Provide a read-only view of the object

Sometimes you might want to make sure that you don’t modify the object in any way and use it only for reading purposes. JavaScript provides object.freeze () to do this, but the behavior is more customizable when using a proxy.

const importantData = {
  name: 'John Doe'.age: 42
}

const handler = {
  set: 'Read-Only'.defineProperty: 'Read-Only'.deleteProperty: 'Read-Only'.preventExtensions: 'Read-Only'.setPrototypeOf: 'Read-Only'
}

const proxy = new Proxy(importantData, handler)
Copy the code

Now, when you try to change an object in any way, you just get a string that says read only. Otherwise, you may raise an error indicating that the object is read-only.

3. Private properties

JavaScript itself has no private properties, except closures. When the Symbol data type was introduced, it was used to mimic private attributes. But as the Object. GetOwnPropertySymbols method is introduced, it was abandoned. ES6 agents are not a perfect solution, but when push comes to shove they can get the job done.

A common convention that allows you to use ES6 proxies is to identify private properties by underlining their names.

const object = {
  _privateProp: 42
}

const handler = {
  has: function(target, key) {
    return! (key.startsWith('_') && key in target)
  },
  get: function(target, key, receiver) {
    return key in receiver ? target[key] : undefined}}const proxy = new Proxy(object, handler)
proxy._privateProp // undefined
Copy the code

Adding ownKeys and deleteProperty brings the implementation closer to true private properties. Then, you can still view the proxy object in the developer console. If your use case is consistent with the above implementation, it still applies.

Why and when to use proxies

ES6 agents are not ideal for performance-intensive tasks. That is why it is vital to carry out the necessary tests. Proxies can be used anywhere an object is expected, and they provide complex functionality with just a few lines of code, making them ideal for metaprogramming.

Proxies are often used in conjunction with another metaprogramming feature called Reflect.


Source: blog.logrocket.com, written by Eslam Hefnawy Follow, translated by public account: Front-end Full Stack Developer