javaWhen I first read about Symbols coming to JavaScript, I couldn’t figure out what the point was or even what they were, so I just kind of parked it. Lately I’ve been doing some work with Async Iterators, and it all fell into place. If you’re still digging into V8 for Apps Script you may not have come across Symbols, or like me, you didn’t see the point of them, let’s see if this article can shed some light and explain what Symbols are all about.
What is a Symbol ?
It’s a new type of primitive (like string, number etc) but it has some odd characteristics.
- Every time you create a Symbol it has a unique value.
- You can’t convert to a string and log it out or export it – so it’s a thing you use inside your App
- There are some ‘well known’ Symbols which are built in to JavaScript with special meaning
- At first it’s hard to imagine what you would need them for
Symbols are unique
Every time you create a Symbol, it’s different to any other Symbol
You can label a Symbol
Although you can assign a label to a symbol, that label has no effect on its value
Labels are for debugging
The point of labels is so you can inspect a label you may have attached to a symbol for debugging purposes. typeof Symbol just returns ‘symbol’. The .toString() method just returns ‘Symbol’, or if you’ve given it a label ‘Symbol(label)’
So what’s the point of Symbols
So far, they seem pretty pointless right ? A thing that we can create but can’t then inpect what it is we’ve created. But wait…
Safe property names
Let’s say you get an array of object from an API that looks like this
You want to enhance that object with a property of your own – say ‘id’. You don’t know if that object already has an id field, (or may have in the future), so you can’t just add a field called ‘id’. You could make up an unlikely/unique property name, but then it’ll get messy elsewhere in your app. Instead you can use a Symbol as a property name, ,with confidence that it won’t conflict with any other property name already in the object, or that you may add elsewhere. Here’s I’m using a Symbol and adding a debug label of ‘id’
Property missing when logged
But when I log that enhanced object, the property with the Symbol for ‘id’ is missing
Property missing when Stringified
When the object is stringified, it’s missing too
Property missing from Object.keys
It’s missing from this too
Hidden properties
This seems odd at first, but it is by design. Code prior to Es6 wouldn’t have known about symbols so enhancing them now might break things. Also since Symbol properties only exist inside your app, there’s not much point in stringifying them, as the value of a Symbol is non exportable and of no use externally anyway.
This is the concept of ‘hidden properties’ – only visible if you know how to look for them (but see later), and only inside your app (but also see later)
Spreading
Making a copy of an object by spreading retains the hidden properties
Reflect
Reflect methods are an Es6 improvement on many of the Object methods, and Reflect can see Symbol properties
Object
Although most Object methods don’t see Symbol, properties there’s a new method to see just Symbol properties
Getting all the properties and values
Now we can get all the properties using Reflect.ownKeys
Or just the ones that are Symbols
using Object.getOwnPropertySymbols
Global Symbols
Although we know now how to get hidden properties using one of the methods above, it doesn’t tell us what the property is (other than via any optional labelling). Knowing the Symbol value for a given hidden property will get you there, but now you have to persist that symbol throughout your app either as a global value, or by passing it around as an argument, both of which could get messy.
Registering a global Symbol
There is a global store for symbols that you can access anywhere in your app, and you can add symbols to it using the Symbol.for() methods. Just like a local Symbol each one is unique, except this time, passing a name that’s already been created will return the same symbol as before
Retrieving the name of a global symbol
If you have a global symbol, you can retrieve the name it represents
Assigning global symbol property names
It’s exactly the same process as for local symbols
Checking for existence of property
As previously shown, Reflect can see symbol properties, so it can be used to check on the existence of a hidden property
Global versus local symbols
This example shows global symbols are accessible from anywhere, whereas for local ones, you must already know the Symbol
Well known symbols
JavaScript has some built in Symbol properties. These are used to reference a range of methods for the object in which they exist. Think of .toString() and .valueOf() which exist in many objects are are used to provoke a certain formatting of an object. For example, console.log will try these methods to format an object for logging.
We all avoid property collisions by simply avoiding toString and valueOf properties or methods when creating an object, as they would override those in the object prototype. As more functionality arrives in JavaScript, it’s clearly not really a sustainable approach, and if JavaScript were to introduce new ones at this stage, they may well collide with methods already existing in your own objects.
That’s where well known (or built in) symbols come in. There are quite a few already in JavaScript, but in this article I’m just going to look at a couple.
Symbol.toPrimitive
This is a little like the .valueOf and .toString methods of an object combined.
console.log(object) – already has a default way of coercing an object so it can be displayed, but Symbol.toPrimitive is a new method that gives it a few more bells and whistles. Think of these 3 cases
- console.log(+object) -> convert the object to a number and print the result // NaN
- console.log(`the color is ${object}`) -> convert the object to a string and print the combined string // the color is [object Object]
- console.log(object) -> just try to print out the objects contents // { color: ‘red’, id: 1 }
The hint argument can be either string, number or default and will indicate the treatment to apply to the object.
Symbol.iterator
More interesting is the iterator Symbol. (there’s also a Symbol.asyncIterator which is a different method for dealing with async operations – for example of one of these see The nodejs Drive API, service account impersonation and async iterators )
When you do these things, it’s the method referred to by the built in Symbol.iterator that’s handling the iteration mechanics
Checking for an iterator
We can check to see if something has a built in iterator using Reflect.has
Make your own iterator
You can change the iteration behavior simply by making your own. Here’s roughly what the built in Array iterator does
Using your own iterator
Now I can make my own iterator for my data array and it will use it instead. Of course this one just does the same as the built in one.
Tweaking the iterator
Let’s make a fancier version of an Array iterator. In this case, we’ll add an option so that the values can be presented in reverse order if requried.
Using the tweaked iterator
Creating a brand new iterator
How about we make an iterator for an object (objects don’t have their own built in iterator), sort the keys alphabetically, and return an array of {key, value} for every property in the object
Using an object iterator
Summary
There’s a lot more to say about Symbols, and in particular, how they have been used in Es6 to add new capabilities, but the intention was simply to try to demonstrate to you (and me) that there’s a lot going on with Symbols and they are a valuable addition to JavaScript (and Apps Script)
- A handier way of accessing Google Drive folders and files from Apps Script
- Apps Script V8: Arraybuffers and Typed arrays
- Apps Script V8: Arraybuffers and Typed arrays, endianness and views
- Apps Script V8: Keystore for global space
- Apps Script V8: Maps and Sets
- Apps Script V8: Multiple script files, classes & namespaces
- Apps Script V8: Provoke server side code from add-ons and htmlservice
- Apps Script V8: Sorting out the call stack to figure out who called
- Apps Script V8: spreading and destructuring
- Apps Script V8: Template literals
- ES6 Symbols – what on earth is all that about ?
- JavaScript V8 Arrow functions, this and that
- JavaScript V8 variable scopes
- Proxy implementation of Apps Script GraphQL Class
- Resuscitating the Apps Script execution transcript – JavaScript Proxy and Reflect to the rescue