blog content
Metaprogramming with ES6
Have you ever wondered about how do the things work in JavaScript under the hood? I think it’s a very interesting topic for everyone who’s ever touched this language. But before you spend hours digging through TC39’s ecma262 specification and the core.js repository maybe, it’s a good idea to take a look at what we can achieve with simple knowledge of ES6 APIs. Back in the day we used to use the eval() function that let us evaluate JavaScript code in a string.
So expression like this:
Would work pretty much everywhere where you can execute JS (there are some exceptions though). This is very handy when it comes to hacking, but you shouldn’t really rely on it when developing real-life applications.
But what do we do if we want to change some behaviour of our JavaScript or reveal information about our code that is not really visible to normal users? ES6 comes with a few useful APIs that can help us achieve exact goals. Today I’ll try to help you understand them even if you’re not a Senior JS Programmer with 10 years of experience 😁.
Symbol
First thing that TC39 brought us is a new data type called Symbol. Symbols are unique. Symbols are immutable. Symbols are amazing 🎉. It’s useful to know how Symbols work and what exactly they can be used for, since they can be a really powerful weapon in your hands.
So let’s start from the very beginning. Do you remember what I’ve just said? Symbols are unique. I mean really unique.
Ok, but what about that? A symbol is a unique and immutable data type. Its new primitive which can be used as object keys just like strings. Example below will illustrate this uniqueness better:
Huh — that can be useful, right? If you didn’t notice, we can define keys which look the same but are completely unique. That’s a big advantage when it comes to hacking. But this is not the last thing that Symbols bring us.
If you started your journey with programming with a different language, then you would probably wonder how to implement private fields in JS class. Well — Symbols can help you achieve a similar thing. Why similar? Because the information is still accessible, but not really visible at first glance. Let’s take a look at the example below:
In this case, your ‘crazy’ Symbol won’t be visible until you call Object.getOwnProperties() on your instance. That’s pretty handy when you don’t want to expose internal details.
Ok — that’s already something. But at the beginning we talked about overriding some language defaults. There are some pre-defined Symbol constants in JavaScript and that’s where they can show off their power. The most common constant is Symbol.iterator — it can help us change JavaScript’s for of iteration. So if we want to print every second element of an array instead of each one, we can follow this example:
Magic.
Another useful constant is Symbol.hasInstance. It can be used to change the mechanics of the instanceof operator. We can easily specify if a given variable is an instance of our class. We don’t even need to extend the class in any way, just specify a static [Symbol.hasInstance] function and enjoy the result.
Symbols are very useful if you want to do something differently within String methods. If you need String.match(), String.replace() or String.search() to provide you a different result, then you can pick between Symbol.match, Symbol.replace and Symbol.search. I’ll just show you how usage of the last one can be implemented.
There is much more to learn about Symbols. But since it’s not the only feature i want to show you today, I’ll send you back to mozilla docs.
Reflect
6th edition of EcmaScript brings us a new built-in object called Reflect from Reflection API. You cannot create a new instance of Reflect, but it exposes a few handy methods which can be used to reveal information about objects and provide some nice syntax sugar for old methods.
If you had read this article carefully, you would have probably noticed that thanks to symbols we can hide some of the information about an object at first glance. Reflect objects can help us discover some lower-level information about an object and all its properties and symbols with just one function.
As you can see, we can discover all of the information about an object (including its symbols). You can think of this method as a mixture of Object.getOwnPropertyNames() and Object.getOwnPropertySymbols().
Have you ever used Object.create() ? It’s a method used to create instances of an object. But it has its pitfalls — what if we want to create an instance and call the class constructor with our new argument? Then we should probably consider using Object.call().
Yeah — almost everything is achievable, but why not make it easy. Just watch how easily we can construct a new date with Reflect compared to old Object’s way.
As we’ve just seen how to create an object with Reflect, now it’s time to do some damage to objects. We can use our new friend to delete the object property. Simply call Reflect.deleteProperty() and free your object from unwanted props.
If you know JS’s delete operator then it’s almost identical, but Reflect.deleteProperty() ignores type checking and works only for objects, not for any kind of variable.
Everything we just learned about Reflect is related to Objects — but it also provides some improvements over Function’s .call() and .apply(). Now, instead of calling both those functions over your function, you can simply use Reflect.apply(). It’s a really nice piece of syntax sugar over chaining those two. Just watch.
Proxies
I know that there might be quite a lot of new things already, so I’ll try to keep this one short. So Reflect brings us quite a few amazing methods (you can check full list here). Proxies are used to intercept all those methods for a chosen object. I’ll show you a quick example of overriding JavaScript’s default delete operator with a proxy. Remember, that’s not the only method you can override with proxy! You can basically do the same for any method exposed by Reflect.
As you can see, the width persisted through deletion. That’s quite a powerful property when it comes to modifying default behavior.
Conclusion
Today we went through some fairly basic, but really useful tools that can help us customize JavaScript to suit our needs better — at least within our application 💪🏻. If you’re interested in this topic, and want to dig deeper into possibilities of modifying defaults, please refer to TC39’s docs. There is a large amount of information that could help you write secure and amazing JS apps.
Mike.