Skip to content

Notes and examples for a talk covering the ES6 metaprogramming types and techniques

Notifications You must be signed in to change notification settings

davefancher/js-metaprogramming-old

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JS Metaprogramming

Metadata, data about data, is everywhere. We seem to intrinsically understand that using data to further describe the data within our systems brings numerous benefits to taming complexity. It follows then that metaprogramming, programming that interacts with the program itself by inspecting or even manipulating its own code can bring similar benefits to our software.

ES6 greatly expands upon JavaScript's existing metaprogramming capabilities with the Symbol, Reflect, and Proxy types. Through some practical examples we'll discuss the role each of these types play within JavaScript metaprogramming and see how they not only affect your code but even drive several modern language features.

What is Metaprogramming?

Wikipedia defines metaprogramming as "...a programming technique in which computer programs have the ability to treat other programs as their data." At a high level this encompasses a variety of techniques including code generation, analysis, and manipulation of existing code.

What we're interested in for the purposes of this discussion are the techniques which allow us to write code that adapts itself to the situation at hand.

Metaprogramming ultimately falls into three categories:

  • Generation
  • Reflection/Introspection
  • Intercession

JavaScript has always supported some degree of metaprogramming, particularly in the areas of code generation and reflection. Many of those "legacy" approaches are still valid and useful but JavaScript has definitely evolved in these areas and now provides several newer types that make metaprogramming much easier and even introduce some new possibilities.

As we explore metaprogramming in JavaScript we'll look at both the old approaches and the new.

Generation

Code generation is exactly what it sounds like: code writing code. In its simplest form this is passing a string that contains some executable code to the eval function (which you should almost never do, by the way).

  • eval - evaluates a string as JavaScript in an unsafe and inefficient manner [Example]
  • Function constructor - defines a new function from a string with additional security considerations [Example]
  • VM module (node.js) - evaluates a string as JavaScript in a configurable context [Example]

As useful as these techniques may appear on the surface unless you're specifically trying to run code stored externally (such as custom, user-defined validation rules) there's usually very little reason to use them since there are typically better, safer approaches. And to be perfectly honest, I really struggled to come up with demonstrative examples here because I'd never do the things as demonstrated.

That's not to say that code generation isn't a useful tool in some situations but JavaScript's dynamic nature really minimizes its usefulness.

Reflection/Introspection

Code generation in JavaScript might not be terribly useful but the same cannot be said for the other two categories. Let's begin with something else that has been around since JavaScript's earliest days: Reflection.

Reflection, or introspection, allows us to inspect and even modify our programs as they're running! This can include everything from inspecting object structure to determining object types to dynamically modifying data structures.

Operators

Even in JavaScript's earliest days we had some limited support for reflective programming. This was (and in some cases still is) typically accomplished through some operators. Here are some highlights:

  • in operator - returns true or false based on whether a property is present in the object or its prototype chain [Example]
  • delete operator - allows removing a property from an object [Example]
  • typeof operator - returns a value's underlying type name but is unreliable. Use other approaches instead. [Example]

Although the various operators we've discussed certainly have their place they can be cumbersome to work with and definitely follow some obsolete patterns. Fortunately as JavaScript has evolved so has its metaprogramming capabilities.

Object Functions

One key area where reflective programming capabilities have expanded is with the Object type itself. Rather than relying on codifying the metaprogramming capabilities into the language itself with keywords and operators the Object type now exposes a variety of reflective and introspective functions. Although not as robust as in other languages this suite of functions enables an impressive range of possibilities in a much more expressive manner. Let's tour some of the more useful and interesting functions.

Reflect Type

Introduced with ES6, the Reflect type defines some alternative reflection and introspection functions to those provided by the Object type. What sets the Reflect type's capabilities apart from Object's is that unlike the Object functions, the Reflect functions are intended to work with some internal things that would otherwise be "hidden" from our code.

Despite the many similarities the Reflect functions stand apart from the Object functions in that they generally provide more consistent and expected behavior mainly in regard to throwing errors when acting upon types that shouldn't be acted upon in that manner.

Again, some highlights:

Symbol Type

Also introduced with ES6, Symbols aren't inherently part of JavaScript metaprogramming but they do factor into it in several important ways. Before we see how Symbols apply to metaprogramming let's first learn about what Symbols are.

Symbols factor into metaprogramming by providing a mechanism by which we can safely extend objects, including the built-in types, without fear of conflicting with existing definitions. They can do this because every Symbol is guaranteed to be a unique instance when created with the Symbol constructor. This not only gives us a convenient way to add custom functionality but indeed also serves as the basis for numerous modern language mechanisms in a manner similar to that of how .NET interfaces drive C# language features such as using and foreach.

Intercession

In metaprogramming intercession is about intercepting behavior. Prior to ES6 JavaScript provided only a few very limited intercession mechanisms. We already got a glance of these features in our Symbol discussion but let's take a closer look.

ES6 introduced a new type entirely focused on intercession. The Proxy type allows intercepting a wider range of activities in affecting an arbitrarily wrapped object through a series of special handlers called "traps". These traps correspond exactly with the functions provided by the Reflect type so there's a convenient symmetry between the two objects that eliminates the guesswork that often comes from inconsistent interfaces.

Let's take a look at the previous example instead implemented as a proxy.

I'll readily admit that I've had little use for Proxy in my day-to-day work so it is a weak point in my understanding of JS metaprogramming but I can definitely see cases where it would be useful, particularly when interacting with 3rd party objects or providing more dynamic data structures.

Wrapping Up

Metaprogramming in JavaScript has evolved greatly over the years starting with its humble beginnings with a series of operators to the more robust capabilities provided by the Object, Reflect, Symbol, and Proxy types we have today. It may not yet be as powerful as in other languages but I've nevertheless found it to be an invaluable tool for building a maintainable platform relies heavily on coding conventions over configuration or allowing for changing the language semantics such that the code I write is more expressive than would normally be possible.

About

Notes and examples for a talk covering the ES6 metaprogramming types and techniques

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published