Exciting ES2015 Features

Last updated January 14th, 2016

Internet Explorer 8 was released 6 years ago. It's the last browser with a nontrivial market share that is stuck on ES3.

Surprisingly, the biggest complaint that I hear about JavaScript on IE8 is having to deal with trailing commas in object
literals. This is a solved problem. ES5 brings a whole lot more to the table than
giving the syntax some slack. Getters/setters/properties were introduced in ES5, but I'm hard pressed to find any
libraries which use them to their advantage.
Object.freeze(),
Object.seal(), and
Object.preventExtensions()
are extremely useful defensive mechanisms which help programs enforce their invariants. Using these new(ish) ES5
features could have huge impacts on how we read and write JavaScript. So where are they?

Wait, I thought this was about ES2015 (ES6).

I'm afraid that the features that are getting the most attention in ES2015 are similar to that trailing comma. This new
version gives us much more than light syntactic sugar (as opposed to heavy syntactic sugar, like
Haskell's do notation).
ES2015 gives us tools that can (and hopefully will) change how JavaScript is written and read. Let's dive in.

Here's a bold claim: the majority of JS written today uses pointers. Yes, pointers: like in C. I know this sounds a bit
crazy, but bear with me. This is because an Object cannot have keys which are themselves Objects.

In other words, we allocate things that live elsewhere and are given a primitive string or number which we later
dereference in order to get at its underlying meaning. These are the fundamental pointer operations. And just like in C,
bad things happen when these "pointers" are passed to functions which expect pointers of a different type, when they are
pre-calculated, or when they refer to objects which have since been deleted.

Why is this? Ultimately, the only tool we have to efficiently create an association between an object and external
metadata is the built-in Object, which can only take strings as its keys.

ES2015's Map allows us to create an association between an arbitrary Object and arbitrary data. It lets us do this
without resorting to
dirty tricks which either won't work
if we use
Object.freeze() or
require searching through arrays of key-value pairs to find an identity match.

So let's look at an example. Imagine a different implementation of setTimeout and clearTimeout, which uses Map
internally to verify that only setTimeout handles are passed to clearTimeout:

This example clearTimeout can only be called with values returned from setTimeout exactly once. By adding an extra
layer of protection, we can ensure that our API is used as designed and future results cannot be "predicted". If the
same approach is taken for requestAnimationFrame and clearAnimationFrame, an attempt to call
clearAnimationFrame(setTimeout(func, ms)) would result in a TypeError exception. This makes our contracts and
invariants explicit. If we bake the limits of our software's design
into our APIs, there are fewer opportunities to surface bugs. On the other hand, if we execute
clearAnimationFrame(setTimeout(func, ms)), we'd be sad to discover that the callback func is called after ms
milliseconds and there are no exceptions raised or errors logged.

With Map and Set, we can write APIs which prevent the construction of references and associate external data with an
object without modifying it. We can move away from the primitive obsession
forced upon us by ES5 and before.

This brings us to our next related feature: the ability to store associations in a memory-efficient way.

Let's say we want to associate data with an arbitrary object. We could use a Map where the keys are the object we
want to add associated data to, and the values would be the associated data. If we use this, we can add associated
data without modifying the original object, but we'll need to be aware of when the object is no longer referenced in
order to .remove() it from the map so that our program doesn't leak memory.

It's a bad practice to mutate objects that we don't own, But in ES5, when we want to associate data with an object that
we don't own and don't want to mutate (and doesn't have a disposal notification mechanism), we're out of luck:
we'll either have to mutate the object or will be forced to leak memory.

What can we do here? One of JavaScript's design trade-offs is that it doesn't tell us when objects are freed. It would
be amazing if we could be notified that an object is completely unused so we could remove it from our Map. But we
just can't know when data is dereferenced unless we build a reference counter, track all of our objects' references, and
build a subscription notification mechanism when our objects are fully dereferenced. This is a pain, it'd be building
our own garbage collector.

Thankfully, there's another way! WeakMap is a new interface which lets us associate without having to leak memory,
mutate objects, or use our own home-grown garbage collector.

This can be a bit abstract, so let's go through a simple example:

A Twitter clone has Tweet and User models. We need to create an association of Tweet to an array of Users which
represents the users who have favorited the Tweet. How can we do this without adding a new field to the Tweet model?

In ES5, we'd probably end up creating a new TweetFavorites interface which held a mapping between Tweet
and a set of Users. It'll probably look like this:

This code has the same problem we just described. Each item we add to the array returned from .getFavoriteUsers() is
now (and forever) strongly referenced by this TweetFavorites interface. If the rest of the application decides to
remove all references to a specific Tweet, our TweetFavorites implementation would still hold on to the Tweet as a
key in its internal Map and all of the items in its associated array. Without any references remaining, we leak memory
for the duration of the application.

To solve this, we would need to manually delete the Tweet's favorites array within the TweetFavorites implementation
when a Tweet is "disposed" and ready to be removed from the rest of the application. This might look something like
this:

But if the Tweet interface did not have an .onDispose() subscription, we'd be out of luck.

But we can use a WeakMap! A WeakMap is just like a Map except it weakly holds on to its keys. When the garbage
collector comes around looking for unreferenced objects, it treats the keys in our WeakMap as if they are not there.
If one of the WeakMap's Tweet keys has all of its references removed, it's as if .delete() was called, and is
allowed to be garbage collected.

Here's what our TweetFavorites implementation would look like with WeakMap:

No matter how hard we try (aside from using a debugger, overriding the WeakMap.prototype methods, or calling
toString on the function), an outside observer will not be able to access our private data! And unlike Crockford's
suggestion of how to create true privates with closures, the function
implementations all live on the prototype, so this approach is much more memory-efficient.

Similar to WeakMap is WeakSet, which is great for "tagging" objects that may eventually go away (like keeping track
of Tweets that we have viewed in our session so we can display them differently). It works like this:

We can add an item to the set and check if items are in the set. If an item is held weakly in the WeakSet, but is
dereferenced everywhere else and unreachable (which means we can't check if it is present in the set), the
garbage collector will also remove it from the set for us. A WeakSet is identical to a WeakMap where the values
are all true (or null or false or any other single value).

WeakMap and WeakSet make it easier for us to build small and low-coupled components without having to worry about
object lifecycle clean up.

With weak references, we could label our objects properties with explicit ownership semantics (like how we tag members
as strong or weak in Objective-C @property declarations) and rely on the garbage collector (or something like ARC)
to keep our memory footprint efficient.

It's important to note that this isn't a radical request. Weak references are present in practically every language which has a
garbage collector or some form of automated memory management:
C#,
C++,
Haskell,
Java,
Lua,
Objective-C,
OCaml,
PHP,
Perl,
Python,
R,
Ruby.

Despite having incredibly good introspection facilities, we are still limited in what we could simulate with
user-defined objects. Proxy lets us create objects with powers that only browser vendors had access to.

One of the surprisingly complex things that jQuery does is make each jQuery object act like an array-like value. It
makes $('.foo')[0] act like $('.foo').get(0). The internal
jQuery.merge function is responsible
for maintaining this array-like behavior.

But with Proxy, we can create array-like objects without having to define every single index. We can react to the
internal
[[get]]
method by creating a Proxy on an object which declares the get action.

As a counterpoint to Proxy, Reflect gives us the ability to trigger these same fundamental operations as function
calls. This is mostly helpful for writing introspective code in a functional style. Here's how it works:

const declarations give us immutable references, but the objects it refers to is still able to change contents.
Object.freeze()and a handy deepFreeze
wrapper gives
us immutable objects. Together, we can have real immutability.

The bad news is that there still isn't a property on String which returns the number of Unicode code points.

The good news is that we will have the ability to read and create Unicode code points using
String.prototype.codePointAt() and String.fromCodePoint()! Unfortunately, there's a really unfortunate wart in
the design of String.prototype.codePointAt(), which means we need to be careful:

// Get a simple character from a string:'x💩x'.codePointAt(0) === 0x78;
// 0x78 is the Unicode code point for the letter 'x'// Get a complex character from a string:'x💩x'.codePointAt(1) === 0x1F4A9;
// 0x1F4A9 is the Unicode code point for '💩'// WARNING: the following does not make much sense'x💩x'.codePointAt(2) === 0xDCA9;
// 0xDCA9 is the *second surrogate pair* of the// UTF-16 code unit pair for '💩'// Get a simple character from a string:'x💩x'.codePointAt(3) === 0x78// 0x78 is the Unicode code point for the letter 'x'

Though things have gotten slightly better, we still must be very careful when dealing with individual "characters" in
strings, especially when performing validation on user input.

ES2015 has the opportunity to change how we write JavaScript. Unfortunately, it is impossible for most of these benefits
to be achieved with the use of ES2015 to ES5 compilers like Babel or Closure
Compiler.

I can't wait until the day we live in a world where we can ship raw ES2015 source code to be executed natively on the
browsers we support.

Lastly, a big thanks to Daniel Espeset and Dan Na for reviewing
a draft of this and providing valuable feedback.

Do you want to learn more? Was something confusing? Was something insightful? In the NYC area
and want to grab a coffee? Feel free to drop me an email at
sufian@gmail.com or send a tweet my way
@sufianrhazi

Disclaimer: Unless stated otherwise, the above words are my own and do not represent the
opinions of any person or business but myself.