Why I'm Excited about Names in JavaScript

Last updated:

There's a ton of exciting activity taking place in TC39, which is the standards group responsible for handling JavaScript (known as ECMAScript there, as ECMA is the standards body that TC39 is part of). One in particular that I like quite a bit is Names. It's not very obvious why this is so good, though, or what advantages these have over other, similar proposals or similar features in other languages.

The Hell are Names?

You know how you access properties on an object with strings? In the Names proposal, you can additionally use a Name object to access properties. This is important, because Name objects are unique and unforgeable. In other words, unless someone gives you the Name object, there is no possible way for you to access that property.

There's another aspect, which is the ability to make some of these Names private. I'll distinguish the two by calling the original thing Unique Names, and the private things Private Names.

If you're familiar with the term "Object Capabilities", Names are a building block for that technology.

What's the Use of a Unique Name?

Having a guaranteed-unique name is wonderful. Many things that are cumbersome or even practically impossible without them become trivial once you have them.

If you're familiar with Lisp, unique names generated with GENSYM are absolutely necessary to write robust macros; without them, you stand a decent chance of accidentally colliding with existing names and breaking code in mysterious ways.

In JS, Unique Names are great for adding "interface" properties to an object. Let's say you define an Iterator interface, plus a bunch of functions that take Iterators and do cool stuff with them. When someone passes you an object, you want to check if it's an Iterator, and if not, ask it to give you an Iterator. For example, if someone passes you a string, you should be able to ask it for an iterator over the characters.

How do you ask for this, though? The simple answer is to say that objects need to have a particular method, getIterator() or __iterator() or something, that returns an iterator. This isn't great, though - what if they already have a property by that name that does something else?

Another way, if you have control over the language, would be to define a set of functions on something the language controls, like Object.setIterator(), Object.getIterator(), and Object.deleteIterator(), which maintain an internal map of classes to iterators. This is just plain cumbersome and ugly, though.

Using a Unique Name, though, this is easy! You just create a special Name and store it somewhere publicly accessible. When someone wants their object to expose an iterator, they just grab the Name you've created, and store their method using it. This'll look something like:

/* library code */
let Iterator = {
  ...
  getIterator: new Name()
}

/* user code */
function myCoolObject() { ... }
myCoolObject.prototype[Iterator.getIterator] = function(){...}

That's it! In the library, whenever a function that expects an iterator is passed something else, it'll first poke at the object using the Iterator.getIterator Name, and if it finds a function there, it'll just call that to extract an iterator.

The benefit of this little bit of indirection is that it's impossible for the user's class to have a property-collision with the Iterator library's property.

One can do even more extreme things, too. What if you want an object to be an iterator, rather than just provide one? (Analogous to the difference between PHP's Iterator and IteratorAggregate interfaces.) You need the object to expose a couple of properties; at minimum, you probably want something like .current, .valid, and .moveNext(). But, again, the object might already use those properties for something else! (This is especially true for names like this - 'current' and 'valid' are reasonably common property names.) Again, though, you can just use Unique Names for this, and in fact the method is exactly the same as before - just create some Names, store them in an easy-to-reach place, and then other classes can just grab and use those Names to store their implementations for you.

A final use of Unique Names - typing! Right now, there are two decent ways to ask an object what type it is - examine its prototype chain, or do "duck typing" (see if it exposes the properties you expect for the given type). Neither of these are great. Prototype-chain examining means you have to use inheritance for your objects, the sins of which are well-documented elsewhere. Duck typing is unreliable because objects can accidentally expose the properties you want without actually working the way you expect. Using Unique Names, though, we can resolve this. Again, just create a Unique Name and stash it somewhere. When an object wants to declare it's your type, it just takes that Name and sets it to true on itself. BAM, type checking is easy - if you check the property on a random object, you'll either get undefined or true based on whether it claims to be the type or not.

I'm sure there are plenty of other cool things in this vein, but my brain keeps getting twisted back to iterators, so you'll have to come up with them on your own.

Okay, But What About Those Private Names?

Private Names are just like Unique Names, except they've got some extra protections to avoid exposing them accidentally.

You'd think, from the name, that these might be useful for making private properties in JS, but you'd be wrong. JS already has private properties, through closures[^1]:

(function(){
  let privateVar = 'foo';

  function Bar() {
    console.log("Hahaha, only I have access to the privateVar!");
    console.log("But I guess you can look at it:");
    console.log(privateVar);
  }
})();

What Private Names are good for is making protected variables, except ridiculously more flexibly and powerfully.

In most OO languages, if you want to have a property that's hidden to most, but available to some classes other than yourself, you have to mark it protected, and the other classes have to inherit from you. This is pretty horrible, because wanting to use something's private variables is not the same thing as wanting to inherit from it. Some languages like C++ have workarounds for this (the friend keyword in C++), but they're all pretty hacky.

Private Names let you hide properties from the public, and expose them to whoever you want - you just have to give them the corresponding Names. You can even expose different sets of properties to different consumers, just by handing out different subsets of your private Names. There's no need to tie this functionality into inheritance.

Here's yet another cool use. I'll assume you know about Proxies, those cool new JS things that let one object pretend to be another object by supplying functions that get called whenever you try and get a property, set a property, etc. on the Proxy. With that functionality, you can hide all details of what's going on underneath normal-looking syntax.

One use for Proxies is to supply someone with a limited-functionality version of an object. Make a Proxy object, store your original object in it, and make the proxy transparently supply the original object's properties if they're on a limited list, and block them (as if they aren't there at all) otherwise. This lets you supply a lesser interface for an object with a minimum of fuss, while still letting the actual object access all of itself.

This is somewhat limited, though. You have to hide the original object inside the Proxy object (or else there's not much point). Right now, that means you have to use a closure variable, as demonstrated above, to hide it so that only the Proxy's own functions can access it. This means, though, that if you pass the limited-functionality object back to the original API that gave it out, it can't extract the full-powered object back out again! (Any method you supply to allow it to do so would allow someone else to do this as well.) With Private Names, though, this concern disappears. You can just store the original object on the Proxy with a Private Name (storing the name itself both in your original API, and in a closure variable in the Proxy object), and then pull it back out again later with the same name, but no one else can access it! (Dave Herman points out another method to accomplish this.[^2])

But What Actually Makes Private Names Different From Unique Names?

You're right, hidden-interlocutor-in-control-of-this-post's-titles, everything I described above can be done with Unique Names as well. The difference between the two is that Private Names have some different defaults set, and act differently with Proxies (in a different scenario than what I described above).

For starters, Unique Names are just ordinary properties; they just use Name objects instead of strings for their name. So, if you use a for-in loop to iterate through all the properties, you'll see properties that use Unique Names alongside all the string-named ones. On the other hand, properties using Private Names are automatically non-enumerable.

The other difference is their behavior with Proxies. When you set up a Proxy, you assign functions to several operations, like "get a property", which is called whenever someone performs that action on the Proxy. In the case of getting/setting a property, your function is passed the name of the property that's being get/set. If someone attempts to access a property on a Proxy using a Private Name, though, and the system naively passed it through, it would "leak" the name - the owner of the Proxy would then have your Private Name and could use it themselves.

Instead, Private Names automatically get a corresponding Unique Name when they're created. This is stored in the Private Name's .public property. When a Proxy attempts to deal with a Private Name, it instead receives the Private Name's public property. If the Proxy already has access to the Private Name, it can compare the .public is has with what it's received, but if it doesn't, then no private information is leaked. There's no linkage back from the public property to the corresponding Private Name; you can't use the Unique Name stored in the .public property for anything except comparisons.

Care to Wrap it Up?

In conclusion, Names are awesome. They let you do a lot of cool things that are difficult/hacky/impossible to do right now, and they're the building blocks for some important security tools like Object Capabilities.

Here's hoping they make it into JS in the nearish future!

[^1]: Dave Herman, author of the current Private Names proposal, points out that Private Names do make private variables easier. If you want a per-instance closure variable, you also have to define all your methods per-instance, rather than defining them once on the prototype. This is hugely costly if you then create a lot of instances. Private Names let you accomplish this without pain:

(function(){
  let key = new Name();
  function Thingy(x) {
    this[key] = x;
  }
  Thingy.prototype = {
    doStuff: function() { ... this[key] ... }
  };
})();

[^2]: Dave Herman points out another solution. Remember that Object.get/set/deleteIterator() thing I talked about earlier, where you maintain a hidden table of associations? Using WeakMaps, another upcoming feature which, among other things, lets you create a dictionary with object keys, you can create a side-table linking a given Proxy to its original object in the same way. Whether this is easier or harder than using Private Names may vary based on the individual case.

(a limited set of Markdown is supported)