Lesson Description
The "Symbols & Metaprogramming" Lesson is part of the full, JavaScript: The Hard Parts, v3 course featured in this preview video. Here's what you'd learn in this lesson:
Will summarizes Symbols and metaprogramming, features introduced in ES6. allowing developers to override default language behaviors safely and compatibly with legacy code. Understanding this coercion mechanism is key for writing predictable JavaScript and handling implicit conversions effectively.
Transcript from the "Symbols & Metaprogramming" Lesson
[00:00:00]
>> Will Sentance: It turns out that we have a little bit more control than I implied here. Our coerce function did only one thing, Chris, just a reminder, it only ever returned out what? 105. 105. But what if our JavaScript engine, when it kicks off and adds parens? Notice how it added parens to coerce for us automatically, right? That's what made it go and run, coerce, as opposed to just reference it, it ran it. It also automatically inserts some information.
[00:00:34]
Let's do it in blue, some information about what the two primitive specifics are. In this case it was to number, and it literally inserts here the string number, look at that, the string number. So as long as we put a placeholder in our function coerce, which you can call anything, could be hello, my name is Chris, as long as we then go, if hello my name is Chris equals number, is number, then return 105.
[00:01:06]
We called it hint, it's the default people use, but it literally, it's a parameter name we can call whatever we want, and into our local memory, we would go here, and in local. We would have a hint, in this case, be the string number, and in this case as well, in our local hint would be the string number, and then we've written our conditional logic, if hint is the string, string, return the string user, we're like, nah, nah, that's not what happened.
[00:01:47]
If string is, if hint is a string number, return 105, the same thing. If instead though, so we can add different rules, depending on whether we're coercing the object to a number or a string. Which is based on our operator that is being applied to that object. But when JavaScript runs a function stored on the hidden property at at to primitive, if it's coerce, sorry, no, it's, if it's coerce, it's getting late, then.
[00:02:19]
If it was a number to primitive, then the input will automatically be the string number, if instead we had something like. String user stored. Or more likely perhaps. Our string literal. A string literal. User stored, then, if we have our string literal user stored, OK, this is when it's getting really late, our string literal user stored, then we're going to hit, hit off to primitive, within this case, it's a string, and what will we pass in to coerce?
[00:03:07]
What will we pass into to coerce instead, not the string number, but the string what? String. User, what would be passed in, Michael? String string, it's the answer was in the question, don't fear, it would be passed in the string, string. And then, Michael, what will we return, not 105, but what? User, user, exactly. OK people, so what are these profound hidden properties with so much power? We should probably call them semi-hidden properties, in fact, I think I'll call them on the next.
[00:03:45]
Introducing Symbols. ES6 feature for adding semi-hidden properties to objects. These are labels. Unique identifiers, like any label, if I have a label on an object, it's got to be unique, right, on that object, and it is a unique value, these are unique identifiers that we cannot write out directly and so will not override developers' existing code. Not only that, if we had, for example, just called this to primitive and say add it like that, then the developer already has a to primitive function, is suddenly now interacting with a new feature of JavaScript.
[00:04:20]
If instead of having this as a hidden property added in 2015, it was instead that we, the JavaScript now had the ability to always check your, not your at at to primitive hidden property, but just your regular to primitive property, then every person, many, who had a to primitive function on their objects, now they're going to be behaving in an unexpected way. They're actually going to be accessed directly by JavaScript.
[00:04:45]
Because it's suppose if we were not doing a special hidden property, but a regular property called to primitive as a regular string, just a regular property, then everyone who's had one, JavaScript's going to go and start grabbing that and doing whatever's in it automatically, something they never expected. So instead, when you're dealing with a language that has to deal with 20 years of 25 years now of history.
[00:05:14]
30 years of history. When it deals with 30 years of history, it can only make changes that largely are non-breaking backwards. And so, how do you do that? You go, brand new data type that doesn't clash with all prior ones. Not a bad idea, but quite entertaining. At at to primitive's on a string, won't override developers' existing to primitive. What I mean by that is it won't, JavaScript won't go and use their existing to primitive property that was written as a string.
[00:05:40]
Which is, if you were just to add this as a feature where you could just say, put it in to primitive, and be sure that, you know, that from now on is where JavaScript will look for your rules. Well, damn it, I already have my to primitive and for the last 20 years, and all my code depends on that, and now JavaScript's going and accessing stuff that I didn't know was ever going to be accessed by JavaScript.
[00:06:01]
No. Also, looping through an object's properties, we won't find to primitive. Looping through them for the Symbols, we will get access to them, but not the regular properties. Meaning if we have code that grabs all the elements of an object, displays them, for example, we're not going to accidentally access them. But JavaScript lets us use them to get under the hood, access to under the hood features of the language.
[00:06:27]
For example, manually controlled to primitive coercion flow, both to number, to string, and to default, as we saw here, or default meaning we don't have neither of those. These built-in ones in this list here that JavaScript knows to go look at, because we can create our own as well, we tend to not very often, but library developers definitely do. These built in one, JavaScript recognizes are known as well-known Symbols.
[00:06:54]
I feel like that's got to be one of the worst names ever, because it's so uncommitted. They're like well-known, they're not, no, but no, they're literally known. They're not well-known, they're like, well-known suggests some gradation, like, they're pretty well-known. No, no, they're literally explicitly known by JavaScript. They should be called known Symbols, not well-known, which somehow seems less committed.
[00:07:21]
Than known anyway, there you go, well-known Symbols, and they open up metaprogramming. Beyond coercion, we can access many behaviors, we can change how an iterator works, we can change how our async features, we can affect the behavior of classes, all with this backwards compatibility via being semi-hidden, meaning if someone else has got a property like that, it's not going to suddenly start using that for meta-behavior.
[00:07:48]
It can let us override safely default language rules, make implicit behaviors explicit. We now know how to manually control our primitive coercion to number to string to Boolean, and also our full pipeline from objects to primitive, taking full control. This allows us to make our process of coercing explicit. That therefore gives us both flexibility, the ability to benefit from the fact that our data can be coerced and changed as it gets used, which is, can be useful.
[00:08:22]
But also predictability, and there's going to be coercion everywhere, and hopefully with a bit of a map, you can start to navigate it, but in practice, a lot of the time, you are going to need to test out exactly which one has been kicked off. Every API call, every operator triggers these coercions. But what we have to default to, people, is explicit control. Whether that's a call to number, to ensure that the data we get off the DOM is definitely a number where we expect it to be, not a DOM string.
[00:08:57]
Whether that's controlling explicitly our coercion from object to primitive. We want to have control in order that, yes, we benefit from readable code at the browser edge, but it's not unpredictable, which we saw very quickly becomes a problem. One of our tools for that is this fascinating Symbols, particularly known Symbols to JavaScript that let us get manual control over a built-in feature, object to primitive type coercion, and a bunch of other ones that I encourage you to go look at, one of which is around iterators and async tomorrow.
[00:09:31]
Gives us backwards compatible control over type coercion, other building features, and I hope you can see why TypeScript is someone's favorite language and has become such a default, because it automates a lot of the rules that we otherwise get unpredictable behavior from, automates a lot of the control, but I will stress that understanding how coercion. It's under the hood, remains crucial for wielding JavaScript effectively and of course, for answering every interviewer's favorite gotcha questions, and now you can tell them, ah, well, I like to take manual control of my object to primitive coercion using the fascinating, well-known symbol library.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops