If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeSo far, all the new ES2015 stuff has been new language constructs: new syntaxes and keywords, like let
, const
and classes! And that was no accident: these are the most important things to understand.
But ES2015 comes packed with other new features, like new functions and new objects. And mostly, those are easy enough to understand: when you see an object or function you don't recognize, look it up, see how it works... and keep going!
But, there is one set of objects... pun intended... that I do want to talk about. They are, Map
, WeakMap
and... Set
!
Head back into play.js
. Let's experiment with Map
first.
Right now, when you need an associative array, you just create an object: foods = {}
and start adding delicious things to it: foods.italian = 'gelato'
, foods.mexican = 'torta'
and foods.canadian = 'poutine'
. Poutine is super delicious:
let foods = {}; | |
foods.italian = 'gelato'; | |
foods.mexican = 'tortas'; | |
foods.canadian = 'poutine'; | |
... lines 5 - 7 |
At the bottom, of course, we can log foods.italian
:
let foods = {}; | |
foods.italian = 'gelato'; | |
foods.mexican = 'tortas'; | |
foods.canadian = 'poutine'; | |
console.log(foods.italian); |
And no surprise, our console tells us we should eat gelato. Good idea!
In ES2015, we now have a new tool: instead of creating a simple object, we can create a new Map
object. The syntax is slightly different: instead of foods.italian = 'gelato'
, use foods.set('italian', 'gelato')
:
let foods = new Map(); | |
foods.set('italian', 'gelato'); | |
... lines 3 - 7 |
Repeat this for the other two keys. And at the bottom, fetch the value with foods.get('italian')
:
let foods = new Map(); | |
foods.set('italian', 'gelato'); | |
foods.set('mexican', 'tortas'); | |
foods.set('canadian', 'poutine'); | |
console.log(foods.get('italian')); |
Simple and beautiful! And it works exactly like before!
Great! So... we have a new Map
object... and it's a different way to create an associative array. But why would we use it? Because it comes with some nice helper methods! For example, we can say foods.has('french')
:
let foods = new Map(); | |
foods.set('italian', 'gelato'); | |
foods.set('mexican', 'tortas'); | |
foods.set('canadian', 'poutine'); | |
console.log( | |
foods.get('italian'), | |
foods.has('french') | |
); |
And that returns false
. Bummer for us.
It wasn't too difficult to check if a key existed before, but this feels clean.
Map has one other advantage... which is kind of crazy: you can use non-string keys!
Try this: create a new variable: let southernUSStates
set to an array of Tennessee
, Kentucky
, and Texas
:
... lines 1 - 5 | |
let southernUsStates = ['Tennessee', 'Kentucky', 'Texas']; | |
... lines 7 - 13 |
Now we can say foods.set(southernUSStates)
and set that to hot chicken
:
... lines 1 - 5 | |
let southernUsStates = ['Tennessee', 'Kentucky', 'Texas']; | |
foods.set(southernUsStates, 'hot chicken'); | |
... lines 8 - 13 |
Yes, the key is actually an object. And that's no problem!
Important side note: hot chicken is really only something you should eat in Tennessee, but for this example, I needed to include a few other states. In Texas, you should eat Brisket.
Anyways, at the bottom, use foods.get(southernUSStates)
to fetch out that value:
... lines 1 - 5 | |
let southernUsStates = ['Tennessee', 'Kentucky', 'Texas']; | |
foods.set(southernUsStates, 'hot chicken'); | |
console.log( | |
foods.get('italian'), | |
foods.get(southernUsStates) | |
); |
And it works just like we want!
If you're wondering when this would be useful... stay tuned. Oh, and there's one other property you should definitely know about: foods.size
:
... lines 1 - 5 | |
let southernUsStates = ['Tennessee', 'Kentucky', 'Texas']; | |
foods.set(southernUsStates, 'hot chicken'); | |
console.log( | |
foods.get('italian'), | |
foods.get(southernUsStates), | |
foods.size | |
); |
That will print 4. Say hello to the new Map
object!
Tip
You can also loop over a Map
using our new friend - the for of
loop. You can
loop over the values or the keys!
// loop over the keys *and* values
for (let [countryKey, food] of foods.entries()) {
console.log(countryKey, food); // e.g. italian gelato
}
// loop over the keys (e.g. italian)
for (let countryKey of foods.keys()) {
console.log(countryKey);
}
Behind the scenes, the last example uses destructuring to assign
each returned by entries()
to the countryKey
and food
variables. It's all
coming together!
ES2015 also gives us a very similar new object: WeakMap
:
let foods = new WeakMap(); | |
... lines 2 - 14 |
And this is where things get a little nuts. Why do we have a Map
and a WeakMap
?
Let's find out! First try to run our code with WeakMap
.
Woh, it explodes!
Invalid value used as week map key
Map
and WeakMap
are basically the same... except WeakMap
has an extra requirement: its keys must be objects. So yes, for now, it seems like WeakMap
is just a worse version of Map
.
Turn each key into an array, which is an object. At the bottom, use foods.get()
and pass it the italian
array:
let foods = new WeakMap(); | |
foods.set(['italian'], 'gelato'); | |
foods.set(['mexican'], 'tortas'); | |
foods.set(['canadian'], 'poutine'); | |
... lines 5 - 8 | |
console.log( | |
foods.get(['italian']), | |
... lines 11 - 12 | |
); |
Now when I run it, it works fine. Wait, or, does it?
Two interesting things: this prints undefined
, hot chicken
, undefined
. First, even though the ['italian']
array in get()
is equal to the ['italian']
array used in set, they are not the same object in memory. These are two distinct objects, so it looks like a different key to WeakMap
. That's why it prints undefined
.
Second, with WeakMap
, you can't call foods.size
. That's just not something that works with WeakMap
.
Let me show you one other crazy thing, which will start to show you the purpose of WeakMap
. After we set the southernUSStates
onto the array, I'm going to set southernUSStates
to null:
let foods = new WeakMap(); | |
foods.set(['italian'], 'gelato'); | |
foods.set(['mexican'], 'tortas'); | |
foods.set(['canadian'], 'poutine'); | |
let southernUsStates = ['Tennessee', 'Kentucky', 'Texas']; | |
foods.set(southernUsStates, 'hot chicken'); | |
southernUsStates = null; | |
... lines 9 - 15 |
When you try it now, this of course prints "undefined". That makes sense: we're now passing null
to the get()
function.
But what you can't see is that the southernUSStates
object no longer exists... anywhere in memory!
Why? In JavaScript, if you have a variable that isn't referenced by anything else anymore, like southernUSStates
, it's eligible to be removed by JavaScript's garbage collection. The same thing happens in PHP.
But normally, because we set southernUSStates
as a key on WeakMap
, this reference to southernUSStates
would prevent that garbage collection. That's true with Map
, but not WeakMap
: it does not prevent garbage collection. In other words, even though southernUSStates
is still on our WeakMap
, since it's not being referenced anywhere else, it gets removed from memory thanks to garbage collection.
But, really, how often do you need to worry about garbage collection when building a web app? Probably not very often. So, at this point, you should just use Map
everywhere: it's easier and has more features.
And that's true! Except for one special, fascinating, nerdy WeakMap
use-case. Let's learn about it!
Hey Arber,
Thanks for sharing this. Though, you sent a link about .has() method, so contains() is still does not exist. ;)
Cheers!
Yo J.R. Jenkins!
Woh! This is a mystery... even to me! You're absolutely right - this method does not exist... and unfortunately, I can't even remember what the heck I was thinking or where this came from!? It was not removed, it was just simply not ever there :). We'll edit that out of the video - very good catch! I'm glad you challenged us on that :).
Cheers!
Hey Ryan!
No problem, but worry about that edit after you finish the Web Pack Series!! :-) I am on the edge of my seat following along at that one as you have really demystified some aspects of Web Pack that I just couldn't wrap my head around before.
As always thanks for the great tutorials.
-jrj
Ah, awesome! I have *loved* writing the Webpack tutorial. We'll keep having a new piece out each day :).
Cheers!
// composer.json
{
"require": {
"php": "^7.2.0",
"symfony/symfony": "3.2.*", // v3.2.14
"twig/twig": "2.10.*", // v2.10.0
"doctrine/orm": "^2.5", // v2.7.1
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
"symfony/swiftmailer-bundle": "^2.3", // v2.4.2
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.3.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.19
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
"doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.0" // v3.2.2
}
}
You mention that the Map object has a contains method, but I don't see that listed at https://developer.mozilla.o... and testing it in play.js doesn't seem to work. Was this removed or am I missing something?