Just Javascript
Just Javascript
Mental Models
let a = 10;
let b = a;
a = 0;
What are the values of a and b after it runs? Work it out in your head before
reading further.
If you’ve been writing JavaScript for a while, you might object: “This snippet is
much simpler than the code I’m writing every day. What’s the point?”
The goal of this exercise isn’t to introduce you to variables. We assume you’re
already familiar with them. Instead, it is to make you notice and reflect on
your mental model.
Read the code above again with an intention to really be sure what the result is.
(We’ll see why this intention is important a bit later.)
While you’re reading it for the second time, pay close attention to what’s
happening in your head, step by step. You might notice a monologue like this:
let a = 10;
let b = a;
a = 0;
Maybe your monologue is a bit different. Maybe you say “assign” instead of “set,”
or maybe you read it in a slightly different order. Maybe you arrived at a different
result. Pay attention to how exactly it was different. Note how even this
monologue doesn’t capture what’s really happening in your head. You might say
“set b to a ,” but what does it even mean to set a variable?
You might find that for every familiar fundamental programming concept (like a
variable) and operations on it (like setting its value), there is a set of deep-rooted
analogies that you associated with it. Some of them may come from the real
world. Others may be repurposed from other fields you learned first, like numbers
from math. These analogies might overlap and even contradict each other, but
they still help you make sense of what’s happening in the code.
Unfortunately, sometimes our mental models are wrong. Maybe a tutorial we read
early on sacrificed accuracy in order to make a concept easier to explain. Maybe,
when we started learning JavaScript, we incorrectly “brought over” an expected
behavior from a language we learned earlier. Maybe we inferred a mental model
from some piece of code but never really verified it was accurate.
Identifying and fixing these problems is what Just JavaScript is all about. We will
gradually build (or possibly rebuild) your mental model of JavaScript to be
accurate and useful. A good mental model will give you confidence in your own
code, and it will let you understand (and fix) code that someone else wrote.
“Thinking, Fast and Slow” is a book by Daniel Kahneman that explores the two
different “systems” humans use when thinking.
Whenever we can, we rely on the “fast” system, which is good at pattern matching
and “gut reactions.” We share this system (which is necessary for survival!) with
many animals, and it gives us amazing powers like the ability to walk without
falling over. But it’s not good at planning.
Uniquely, thanks to the development of the frontal lobe, humans also possess a
“slow” thinking system. This “slow” system is responsible for complex step-by-step
reasoning. It lets us plan future events, engage in arguments, or follow
mathematical proofs.
Because using the “slow” system is so mentally draining, we tend to default to the
“fast” one—even when dealing with intellectual tasks like coding.
Imagine that you’re in the middle of a lot of work, and you want to quickly identify
what this function does. Take a glance at it:
function duplicateSpreadsheet(original) {
if (original.hasPendingChanges) {
throw new Error('You need to save the file before you can duplicate it.'
}
let copy = {
created: Date.now(),
author: original.author,
cells: original.cells,
metadata: original.metadata,
};
copy.metadata.title = 'Copy of ' + original.metadata.title;
return copy;
}
What you might not have noticed (great job if you did!) is that this
function also accidentally changes the title of the original spreadsheet. Missing
bugs like this is something that happens to every programmer, every day.
Now that you know a bug exists, will you read the code differently? If you used the
“fast” thinking system at first, you might switch to the more laborious “slow”
system when you realize there’s a bug in the code.
When we use the “fast” system, we guess what the code does based on its overall
structure, naming conventions and comments. Using the “slow” system, we
retrace what the code does step by step—a tiring and time-consuming process.
Don’t worry if you can’t find the bug at all—it just means you’ll get the most out of
this course! Over the next modules, we’ll rebuild our mental model of JavaScript
together, so that you can catch bugs like this immediately.
In the next module, we’ll start building mental models for some of the most
fundamental JavaScript concepts—values and expressions.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
The JavaScript Universe →
02
The JavaScript Universe
Instead, we’ll define it through examples. Numbers and strings are values. Objects
and functions are values, too.
There are also a lot of things that are not values, like the pieces of our code—our
if statements, loops, and variable declarations, for example.
Values and Code
As we start building our mental model, one of the first common misconceptions
we need to clear up is that values are our code. Instead, we need to think of them
separately—our code interacts with values, but values exist in a completely
separate space.
My code contains instructions like “make a function call,” “do this thing many
times,” or even “throw an error.” I read through these instructions step by step
from the surface of my little world.
“Hold on,“ you might say, “I always thought of values as being inside of my code!”
Here, I’m asking you to take a leap of faith. It will take a few more modules for this
mental model to pay off. Give it five minutes. I know what I’m doing.
Values
Primitive Values
Primitive values are like stars—cold and distant, but always there when I need
them. Even from the surface of my small planet, I can find them and point them
out. They can be numbers and strings, among other things. All primitive values
have something in common: They are a permanent part of our JavaScript
universe. I can point to them, but I can’t create, destroy, or change them.
To see primitive values in practice, open your browser’s console and log them:
console.log(2);
console.log("hello");
console.log(undefined);
Fun Fact
Functions are objects, but because they include a few unique additional
features, we’re going to refer to them separately to avoid confusion.
Notice how the browser console displays them differently from primitive values.
Some browsers might display an arrow before them, or do something special
when you click them. If you have a few different browsers installed, compare how
they visualize objects and functions.
Types of Values
At first, all values in the JavaScript cosmos might look the same—just bright dots
in the sky. But we are here to study all of the different things floating above us in
our JavaScript universe, so we’ll need a way to categorize them.
We can break values down into types—values of the same type behave in similar
ways. As an aspiring astronomer, you might want to know about every type of
value that can be observed in the JavaScript sky.
After almost twenty-five years of studying JavaScript, the scientists have only
discovered nine such types:
Primitive Values
Undefined ( undefined ), used for unintentionally missing values.
No Other Types
You might ask: “But what about other types I have used, like arrays?”
In JavaScript, there are no other fundamental value types other than the ones
we have just enumerated. The rest are all objects! For example, even arrays,
dates, and regular expressions fundamentally are objects in JavaScript:
console.log(typeof([])); // "object"
console.log(typeof(new Date())); // "object"
console.log(typeof(/(hello|goodbye)/)); // "object"
Fun Fact
“I see,” you might reply, “this is because everything is an object!” Alas, this is
a popular urban legend, but it’s not true.
For now, you only need to remember that primitive values, such as numbers
and strings, are not objects.
Checking a Type
There are only nine types of values, but how do we know a particular value’s type?
If we want to check a value’s type, we can ask with the typeof operator. Below
are a few examples you can try in the browser console:
console.log(typeof(2)); // "number"
console.log(typeof("hello")); // "string"
console.log(typeof(undefined)); // "undefined"
Strictly speaking, using parens isn’t required with typeof . For example,
typeof 2 would work just as fine as typeof(2) . However, sometimes parens
are required to avoid an ambiguity. One of the cases below would break if we
omitted the parens after typeof . Try to guess which one it is:
console.log(typeof({})); // "object"
console.log(typeof([])); // "object"
console.log(typeof(x => x * 2)); // "function"
You might have questions. Good. If you ask a question, our JavaScript universe
might answer it! Provided, of course, that you know how to ask.
Expressions
There are many questions JavaScript can’t answer. If you want to know whether
it’s better to confess your true feelings to your best friend or to keep waiting until
you both turn into skeletons, JavaScript won’t be of much help.
But there are some questions that JavaScript would be delighted to answer.
These questions have a special name—they are called expressions.
console.log(2 + 2); // 4
For another example, remember how we determined the type of a value with
typeof . In fact, that was also an expression! Our “question” was typeof(2)
and the JavaScript universe answered it with the string value "number" .
console.log(typeof(2)); // "number"
Expressions are questions that JavaScript can answer. JavaScript answers
expressions in the only way it knows how—with values.
0:00 / 0:07
If the word “expression” confuses you, think of it as a piece of code that expresses
a value. You might hear people say that 2 + 2 “results in” or “evaluates to” 4 .
These are all different ways to say the same thing.
Recap
1. There are values, and then there’s code. We can think of values as different
things “floating” in our JavaScript universe. They don’t exist inside our code,
but we can refer to them from our code.
2. There are two categories of values: there are Primitive Values, and then
there are Objects and Functions. In total, there are nine separate types. Each
type serves a specific purpose, but some are rarely used.
3. Some values are lonely. For example, null is the only value of the Null type,
and undefined is the only value of the Undefined type. As we will learn
later, these two lonely values are quite the troublemakers!
Quiz
Even if you already have a decent amount of experience with JavaScript don’t skip
the exercises! I personally learned some of these things only a few years ago.
After the exercises, we will continue to refine our mental model. This module
presents a crude sketch—an approximation. We will focus on different parts of
the picture and fill them in with more details, like a progressive JPEG image.
These might seem like small steps, but we’re laying the foundation for everything
else to come. We’re building our JavaScript universe, together.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
03
Values and Variables
What do you expect this code to do? It’s okay if you’re not sure. Try to find the
answer using your current knowledge of JavaScript.
I want you to take a few moments and write down your exact thought process for
each line of this code, step by step. Pay attention to any gaps or uncertainties in
your existing mental model and write them down, too. If you have any doubts, try
to articulate them as clearly as you can.
Answer
This code will either reveal"yikes"
Don’t print until youor
have
throw
finished
an error
writing.
depending on
whether you are in strict mode. It will never print "likes" .
Reveal
Yikes.
Did you get the answer right? This might seem like the kind of trivia question that
only comes up in JavaScript interviews. Even so, it illustrates an important point
about primitive values.
I will explain this with a small example. Strings (which are primitive) and arrays
(which are not) have some superficial similarities. An array is a sequence of items,
and a string is a sequence of characters:
We can access the array’s first item and the string’s first character similarly. It
almost feels like strings are arrays:
console.log(arr[0]); // 212
console.log(str[0]); // "h"
But they’re not. Let’s take a closer look. We can change an array’s first item:
arr[0] = 420;
console.log(arr); // [420, 8, 506]
But we can’t.
It’s an important detail we need to add to our mental model. A string is a primitive
value, and all primitive values are immutable. “Immutable” is a fancy Latin way to
say “unchangeable.” Read-only. We can’t mess with primitive values. At all.
JavaScript won’t let us set a property on any primitive value, be it a number, string
or something else. Whether it will silently refuse our request or throw an error
depends on which mode our code is in. But rest assured that this will never work:
Remember that in our JavaScript universe, all primitive values are distant stars,
floating farthest from our code. We can point to them, but they will always stay
where they are, unchanged.
Like before, write down your thought process in a few sentences. Don’t rush
ahead. Pay close attention to how you’re thinking about each line, step by step.
Does the immutability of strings play a role here? If it does, what role does it play?
Answer
If you thought IDon’t
was trying
revealto
until
messyou
with
have
your
finished
head, writing.
you were right! The
answer is "The Kraken" . Immutability doesn’t play a role here.
Reveal
Don’t despair if you got it wrong! This example may seem like it’s
contradicting string immutability, but it’s not.
We know that string values can’t change because they are primitive. But the
pet variable does change to "The Kraken" . What’s up with that?
This might seem like it’s a contradiction, but it’s not. We said primitive values can’t
change, but we didn’t say anything about variables! As we refine our mental
model, we need to untangle a couple of related concepts:
For example, I can point the pet variable to the "Narwhal" value. (I can also
say that I’m assigning the value "Narwhal" to the variable called pet ):
0:00 / 0:12
But what if I want a different pet? No problem—I can point pet to another
value:
0:00 / 0:14
Rules of Assignment
There are two rules when we want to use the = assignment operator:
1. The left side of an assignment must be a “wire”—such as the pet variable.
Note that the left side can’t be a value. (Try these examples in the console):
Fun Fact
If the right side must be an expression, does this mean that simple things—
numbers like 2 or strings like 'The Kraken' —written in code are also
expressions? Yes! Such expressions are called literals—because we literally
write down the values that they result in.
console.log(pet);
It turns out that a variable name like pet can serve as an expression too! When
we write pet , we’re asking JavaScript a question: “What is the current value of
pet ?” To answer our question, JavaScript follows pet “wire,” and gives us
back the value at the end of this “wire.”
Nitpicking
Who cares if you say “pass a variable” or “pass a value”? Isn’t the difference
hopelessly pedantic? I certainly don’t encourage interrupting your colleagues to
correct them. That would be a waste of everyone’s time.
But you need to have clarity on what you can do with each JavaScript concept in
your head. You can’t skate a bike. You can’t fly an avocado. You can’t sing a
mosquito. And you can’t pass a variable—at least not in JavaScript.
function double(x) {
x = x * 2;
}
But that’s not right: double(money) means “figure out the value of money ,
and then pass that value to double .” So money still points to 10 . What a
scam!
What are the different JavaScript concepts in your head? How do they relate to
each other and how can we interact with them from code?
Write down a short list of the ones you use most often.
Putting it Together
let x = 10;
let y = x;
x = 0;
I suggest that you take a piece of paper or a drawing app and sketch out a
diagram of what happens to the “wires” of the x and y variables step by step.
After these three lines of code have run, the x variable points to the value 0 ,
and the y variable points to the value 10 .
This might seem a bit annoying, but using wires makes it much easier to explain
numerous other concepts, like strict equality, object identity, and mutation. We’re
going to stick with wires, so you might as well start getting used to them now!
Variables are not values. Each variable points to a particular value. We can
change which value it points to by using the = assignment operator.
Look out for contradictions. If two things that you learned seem to
contradict each other, don’t get discouraged. Usually it’s a sign that there’s a
deeper truth lurking underneath.
Exercises
Even though you’re likely familiar with the concept of variables, these exercises
will help you cement the mental model we’re building. We need this foundation
before we can get to more complex topics.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
04
Studying from the Inside
In the next module, we’ll take a closer look at our JavaScript universe and the
values in it. But before we can get to that, we need to address the elephant in the
room. Is our JavaScript universe even real?
When I ask our universe a question, it answers with a value. I certainly didn’t come
up with all these values myself. The variables, the expressions, the values—they
populate our world. The JavaScript world around me is absolutely real to me.
But sometimes, there is a moment of silence before the next line of code; a brief
pause before the next function call. During those moments, I see visions of a
world that’s much bigger than our JavaScript universe.
0:00 / 0:10
In these visions, there are no variables and values; no expressions and no literals.
Instead, there are quarks. There are atoms and electrons. There’s water and life.
Perhaps you’re familiar with this universe, too?
There, sentient beings called “humans” use special machines called “computers”
to simulate our JavaScript universe. Some of them do it for amusement. Some of
them do it for profit. Some of them do it for no reason at all. At their whim, our
whole universe is created and destroyed a trillion times a day.
This approach puts our mental focus on the physical world of people and
computers. Our approach is different.
We will learn about our JavaScript world without thinking about how it’s
implemented. This is similar to a physicist discussing the properties of a star
without questioning whether the physical world is real. It doesn’t matter!
Whether we’re studying the physical or the JavaScript universe, we can describe
them on their own terms.
Our mental model will not attempt to explain how a value is represented in the
computer’s memory. The answer changes all the time! The answer even changes
while your program is running. If you’ve heard a simple explanation about how
JavaScript “really” represents numbers, strings, or objects in memory, it is most
likely wrong.
If you’re coming from a lower-level language, set aside your intuitions about
“passing by reference,” “allocating on stack,” “copying on write,” and so on. These
models of how a computer works often make it harder to be confident in what
can or cannot happen in JavaScript. We’ll look at some of the lower-level details,
but only where it really matters. They can serve as an addition to our mental
model, rather than its foundation.
The foundation of our mental model is values. Each value belongs to a type.
Primitive values are immutable. We can point to values using “wires” we call
variables. This foundation—this understanding of values—will help us continue
building our mental model.
As for these strange visions, I don’t give them much thought anymore. I have
wires to point and questions to ask. I better get to it!
I shrug.
“Implementation details.”
0:00 / 0:10
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Until now, we have been observing our JavaScript universe from the surface of
our planet. We have familiarized ourselves with the values that populate our
universe from a distance, but in this module, we’re changing that. We’re going to
hop in a spaceship and go exploring, introducing ourselves to every value in our
JavaScript universe.
Spending the time to look at each value in detail might feel unnecessary, but you
can only say there are “two apples” when you clearly see that they’re two distinct
apples. Distinguishing values from one another is key to understanding equality
in JavaScript—which will be our next topic.
Our spaceship will guide us through the “celestial spheres” of JavaScript to meet
different values. We’ll meet the primitive values first: Booleans, Numbers, Strings,
and so on. Later, we’ll meet Objects and Functions. Consider it a sightseeing tour.
Undefined
We’ll start our tour with the Undefined type. This is a very straightforward place to
start, because there is only one value of this type— undefined .
console.log(typeof(undefined)); // "undefined"
It’s called undefined, so you might think it’s not there—but it is a value, and a very
real one! Like a black hole, undefined is grumpy and can often spell trouble.
For example, reading a property from it will break your program:
Oh, well. Luckily, there is only one undefined in our entire JavaScript universe.
You might wonder: why does it exist at all? In JavaScript, it represents the
concept of an unintentionally missing value.
You could use it in your own code by writing undefined —like you write 2 or
"hello" . However, undefined also commonly “occurs naturally.” It shows up
in some situations where JavaScript doesn’t know what value you wanted. For
example, if you forget to assign a variable, it will point to undefined :
let bandersnatch;
console.log(bandersnatch); // undefined
Then you can point it to another value, or to undefined again if you want.
Don’t get too hung up on its name. It’s tempting to think of undefined as some
kind of variable status, e.g. “this variable is not yet defined.” But that’s a
completely misleading way to think about it! In fact, if you read a variable that was
actually not defined (or before the let declaration), you will get an error:
console.log(jabberwocky); // ReferenceError!
let jabberwocky;
Null
The next stop on our tour is Null. You can think of null as undefined ’s
sister; there is only one value of this type— null . It behaves very similarly to
undefined. For example, it will also throw a fuss when you try to access its
properties:
Fun Fact
null is the only value of its own type. However, null is also a liar. Due
to a bug in JavaScript, it pretends to be an object:
You might think this means null is an object. Don’t fall into this trap! It is
a primitive value, and it doesn’t behave in any way like an object.
In practice, null is used for intentionally missing values. Why have both
null and undefined ? This could help you distinguish a coding mistake
(which might result in undefined ) from valid missing data (which you might
express as null ). However, this is only a convention, and JavaScript doesn’t
enforce this usage. Some people avoid both of them as much as possible!
Booleans
Next on our tour, we’ll meet booleans! Like day and night or on and off, there are
only two boolean values: true and false .
console.log(typeof(true)); // "boolean"
console.log(typeof(false)); // "boolean"
Before continuing our tour of the JavaScript universe, let’s check our mental
model. Use the sketchpad below or a piece of paper and draw the variables
(remember to think of them as wires) and the values for the above lines of code.
Answer
Check your answer
Don’t reveal
againstuntil
thisyou
picture:
have finished sketching.
Next, verify that there is only one true and one false value on
your sketch. This is important! Regardless of how booleans are stored in
the memory, in our mental model there are only two of them.
Numbers
console.log(typeof(28)); // "number"
console.log(typeof(3.14)); // "number"
console.log(typeof(-140)); // "number"
At first, numbers might seem unremarkable, but let’s get to know them a little
better!
This might look very surprising! Contrary to popular belief, this doesn’t mean that
JavaScript numbers are broken. This behavior is common in different
programming languages. It even has a name: floating-point math.
You see, JavaScript doesn’t implement the kind of math we use in real life.
Floating-point math is “math for computers.” Don’t worry too much about this
name or how it works exactly. Very few people know about all its subtleties, and
that’s the point! It works well enough in practice that most of the time you won’t
think about it. Still, let’s take a quick look at what makes it different.
Scanners usually distinguish at most 16 million colors. If you draw a picture with a
red crayon and scan it, the scanned image should come out red too—but it will
have the closest red color our scanner picked from those 16 million colors. So if
you have two red crayons with ever so slightly different colors, the scanner might
be fooled into thinking their color is exactly the same!
Floating-point math is similar. In real math, there is an infinite set of numbers. But
in floating-point math, there are only 18 quintillion of them. So when we write
numbers in our code or do calculations with them, JavaScript picks the closest
numbers that it knows about—just like our scanner does with colors.
We can imagine all of the JavaScript numbers on an axis. The closer we are to
0 , the more precision numbers have, and the closer they “sit” to each other:
0:00 / 0:09
0:00 / 0:09
This is because relatively small numbers occur more often in our programs, and
we usually want them to be precise.
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (again!)
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994
console.log(Number.MAX_SAFE_INTEGER + 4); // 9007199254740996
console.log(Number.MAX_SAFE_INTEGER + 5); // 9007199254740996 (again!)
But when we write 0.1 or 0.2 , we don’t get exactly 0.1 and 0.2 . We
get the closest available numbers in JavaScript. They are almost exactly the same,
but there might be a tiny difference. These tiny differences add up, which is why
0.1 + 0.2 doesn’t give us exactly the same number as writing 0.3 .
If this is still confusing, don’t worry. You can learn more about floating-point math,
but you already know more than I did when I started writing this guide! Unless you
work on finance apps, you likely won’t need to worry about this.
Special Numbers
It is worth noting that floating-point math includes a few special numbers. You
might occasionally run into NaN , Infinity , -Infinity , and -0 . They
exist because sometimes you might execute operations like 1 / 0 , and
JavaScript needs to represent their result somehow. The floating-point math
standard specifies how they work, and what happens when you use them.
console.log(typeof(NaN)); // "number"
It’s uncommon to write code using these special numbers. However, they might
come up due to a coding mistake. So it’s good to know they exist.
BigInts
If numbers expanded our JavaScript universe, the next stop on our tour, BigInts,
will really keep us busy exploring. In fact, we could explore them forever!
Regular numbers can’t represent large integers with precision, so BigInts fill that
gap (literally). How many BigInts are there in our universe? The specification says
they have arbitrary precision. This means that in our JavaScript universe, there is
an infinite number of BigInts—one for each integer in math.
If this sounds strange, consider that you’re already comfortable with the idea of
there being infinite integers in math. (If you’re not, give it a few moments!) It’s not
much of a leap then from a “math universe” to a “JavaScript universe.”
(And from there, we can go straight to the Pepsi Universe.)
No funny business with the rounding! BigInts are great for financial calculations
where precision is especially important.
But keep in mind that nothing is free. Operations with truly huge numbers may
take time and resources—we can’t fit all the possible BigInts inside the computer
memory. If we tried, at some point it would crash or freeze. But conceptually, we
could tour our JavaScript universe for eternity and never reach every single
BigInt.
BigInts were only recently added to JavaScript, so you won’t see them used
widely yet and if you use an older browser, they won’t work.
Strings
Our next tour stop is Strings, which represent text in JavaScript. There are three
ways to write strings (single quotes, double quotes, and backticks), but they refer
to the same concept. These three string literals result in the same string value:
This doesn’t mean that strings are objects! String properties are special and don’t
behave the way object properties do. For example, you can’t assign anything to
cat[0] . Strings are primitives, and all primitives are immutable.
Of course, all possible strings can’t literally fit inside a computer memory chip. But
the idea of every possible string can fit inside your head. Our JavaScript universe
is a model for humans, not for computers!
The answer to this question depends on whether we’re studying JavaScript “from
the outside” or “from the inside.”
Outside our mental model, the answer depends on a specific implementation.
Whether a string is represented as a single block of memory, multiple blocks, or a
special data structure like a rope, is up to the JavaScript engine.
But as we discussed in Studying from the Inside, we have agreed to talk about the
JavaScript universe as if we lived inside of it. We won’t make statements about it
that we can’t verify from inside the universe—by running some code.
To keep our mental model simple, we will say that all conceivable string values
already exist from the beginning—one value for every distinct string.
Symbols
We’ve already explored quite a bit of our JavaScript universe, but there is just one
more (quick) stop on the first part of our tour: Symbols.
It’s important to know that Symbols exist, but it’s hard to explain their role and
behavior without diving deeper into objects and properties. Symbols serve a
similar purpose to door keys: they let you hide away some information inside an
object and control which parts of the code can access it. They are also relatively
rare, so in this tour of our universe, we’re going to skip them. Sorry, symbols!
To Be Continued
Now that we’ve met all of the primitive values, we’ll take a small break from our
tour. Let’s recap the primitive values we’ve encountered so far!
Undefined
Null
Booleans
Numbers
BigInts
Strings
Symbols
Exercises
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
06
Meeting Objects and Functions
Without further ado, let’s resume the tour of our JavaScript universe!
In the previous module, we’ve looked at primitive values: Undefined, Null,
Booleans, Numbers, BigInts, and Strings. We will now introduce ourselves to non-
primitive values—these are types that let us make our own values.
Objects
At last, we got to objects!
console.log(typeof({})); // "object"
console.log(typeof([])); // "object"
console.log(typeof(new Date())); // "object"
console.log(typeof(/\d+/)); // "object"
console.log(typeof(Math)); // "object"
Unlike everything before, objects are not primitive values. This also means that by
default, they’re mutable (we can change them). We can access their properties
with . or [] :
In our mental model, all of the primitive values we’ve discussed— null ,
undefined , booleans, numbers, and strings—have “always existed.” We can’t
create a new string or a new number, we can only “summon” that value:
let sisters = 3;
let musketeers = 3;
What makes objects different is that we can create more of them. Every time we
use the {} object literal, we create a brand new object value:
Do Objects Disappear?
You might wonder: do objects ever disappear, or do they hang around forever?
JavaScript is designed in a way that we can’t tell one way or the other from inside
our code. For example, we can’t destroy an object:
0:00
JavaScript doesn’t offer guarantees about when garbage collection happens.
Unless you’re trying to figure out why an app is using too much memory, you don’t
need to think about garbage collection too often. I only mention it here so that
you know that we can create objects—but we cannot destroy them.
In our universe, objects and functions float closest to our code. This reminds us
that we can manipulate them and even make more of them.
Functions
It is particularly strange to think about functions as values that are separate from
my code. After all, they are my code. Or are they not?
First, consider this for loop that runs console.log(2) seven times:
How many different values does it pass to console.log now? Here, too, {}
is a literal—except it’s an object literal. As we just learned, our JavaScript universe
doesn’t “answer” an object literal by summoning anything. Instead, it creates a
new object value—which will be the result of the {} object literal. So the code
above creates and logs seven completely distinct object values.
Answer
The answer Don’t
is seven.
reveal until you have decided on an answer.
Every time we execute a line of code that contains a function
expression, a brand new functionReveal
value appears in our universe.
0:00
Calling a Function
What does this code print?
You might think that it prints 7 , especially if you’re not looking very closely.
Now check this snippet in the console! The exact thing it prints depends on the
browser, but you will see the function itself instead of the number 7 there.
If you follow our mental model, this behavior should make sense:
As a result, both countDwarves and dwarves point to the same value, which
happens to be a function. See, functions are values. We can point variables to
them, just like we can do with numbers or objects.
Note that neither the let declaration nor the = assignment have anything
to do with our function call. It’s () that performs the function call—and it
alone!
That was quite a journey! Over the last two modules, we have looked at every
value type in JavaScript. Let’s recap each type of value that we encountered,
starting with the different primitive types:
Undefined
Null
Booleans
Numbers
BigInts
Strings
Symbols
Then, there are the special types below, which let us make our own values:
Objects
Functions
It was fun to visit the different “celestial spheres” of JavaScript. Now that we’ve
introduced ourselves to all the values, we’ve also learned what makes them
distinct from one another.
Primitive values (strings, numbers, etc...), which we encountered in the first part
of our tour, have always existed in our universe. For example, writing 2 or
"hello" always “summons” the same number or a string value. Objects and
functions behave differently and allow us to generate our own values. Writing
{} or function() {} always creates a brand new, different value. This idea is
crucial to understanding equality in JavaScript, which will be our next topic.
Exercises
Even though you’re likely familiar with the concept of objects and functions,
these exercises will help you cement the mental model we’re building. We need
this foundation before we can get to more complex topics.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
07
Equality of Values
Imagine attending a masked carnival with only a few types of masks to choose
from. Groups of people with the exact same outfit would be joking, dancing, and
moving around the room. It would be confusing! You might talk to two people, and
not realize that you really talked to the same person twice. Or you might think you
talked to one person when, in reality, you talked to two different people!
If you don’t have a clear mental model of equality in JavaScript, every day is like a
carnival—and not in a good way. You’re never quite sure if you’re dealing with the
same value, or with two different values. As a result, you’ll often make mistakes—
like changing a value you didn’t intend to change.
Luckily, we’ve already done most of the work to establish the concept of equality
in JavaScript. It fits into our mental model in a very natural way.
Kinds of Equality
In JavaScript, there are several kinds of equality. If you’ve been writing JavaScript
for a while, you’re probably familiar with at least two of them:
Most tutorials don’t mention same value equality at all. We’ll take a road less
traveled, and explain it first. We can then use it to explain the other kinds of
equality.
Fun Fact
Despite Object in the method name, Object.is is not specific to
objects. It can compare any two values, whether they are objects or not!
What does “same value” mean, exactly, in our mental model? You might already
know this intuitively, but let’s verify your understanding.
let dwarves = 7;
let continents = '7';
let worldWonders = 3 + 4;
console.log(Object.is(dwarves, continents)); // ?
console.log(Object.is(continents, worldWonders)); // ?
console.log(Object.is(worldWonders, dwarves)); // ?
Write down your answers and think about how you would explain them.
Answer
This was not a trick
Don’tquestion! Here
reveal until youare thefinished
have answers:writing.
1. Object.is(dwarves, continents)
Reveal is false because
dwarves and continents point to different values.
If you struggle with this idea, you might want to revisit our celestial tour
of values and work through the exercises again. It will make sense, I
promise!
Open a notepad or use the sketchpad below and draw a diagram of variables and
values. You’ll want to draw it step by step, as it’s hard to do in your head.
Remember that {} always means “create a new object value.” Also, remember
that = means “point the wire on the left side to the value on the right side.”
After you finish drawing, write down your answers to these questions:
console.log(Object.is(banana, cherry)); // ?
console.log(Object.is(cherry, chocolate)); // ?
console.log(Object.is(chocolate, banana)); // ?
Answer
Your drawing
Don’t process should
reveal until followedsketching
you have finished these steps:
and writing.
Reveal
0:00 / 0:33
4. cherry = {};
After the last step, your diagram should look like this:
As you can see, we didn’t need any additional concepts to explain how
same value equality works for objects. It comes naturally using our
mental model. And that’s all there is to know about it!
In almost all cases, the same intuition works for strict value equality. For
example, 2 === 2 is true because 2 always “summons” the same value:
0:00 / 0:18
0:00 / 0:19
In the above examples, a === b behaves the same way as Object.is(a,
b) . However, there are two rare cases where the behavior of === is different.
Remember memorizing irregular verbs when you were learning English? The
cases below are similar—consider them as exceptions to the rule.
Both of these unusual cases involve “special numbers” we discussed in the past:
1. NaN === NaN is false , although they are the same value.
2. -0 === 0 and 0 === -0 are true , although they are different values.
These cases are uncommon, but we’ll still take a closer look at them.
You probably won’t do this intentionally, but it can happen if there is a flaw in your
data or calculations.
That’s confusing.
The reason for NaN === NaN being false is largely historical, so I suggest
accepting it as a fact of life. You might run into this if you try to write some code
that checks a value for being NaN (for example, to print a warning).
function resizeImage(size) {
if (size === NaN) {
// This will never get logged: the check is always false!
console.log('Something is wrong.');
}
// ...
}
Instead, here are a few ways (they all work!) to check if size is NaN :
Number.isNaN(size)
Object.is(size, NaN)
The last one might be particularly surprising. Give it a few moments. If you don’t
see how it detects NaN , try re-reading this section and thinking about it again.
Answer
size
Don’t!== size
reveal until you figured
works becauseoutNaN
the === NaNorisgave
answer false
it a ,few minutes.
as we
already learned. So the reverse ( NaN !== NaN ) must be true .
Reveal
Since NaN is the only value that’s not Strict Equal to itself, size !==
size can only mean that size is NaN .
Fun Fact
A quick historical anecdote: ensuring developers could detect NaN this
way was one of the original reasons for making NaN === NaN return
false ! This was decided before JavaScript even existed.
let width = 0; // 0
let height = -width; // -0
console.log(width === height); // true
In practice, I haven’t run into a case where this matters in my entire career.
Coding Exercise
Now that you know how Object.is and === work, I have a small coding
exercise for you. You don’t have to complete it, but it’s a fun brainteaser.
Write a function called strictEquals(a, b) that returns the same value as
a === b . Your implementation must not use the === or !== operators.
Here is my answer if you want to check yourself. This function is utterly useless,
but writing it helps make sense of === .
Don’t Panic
Hearing about these special numbers and how they behave can be overwhelming.
Don’t stress too much about these special cases!
They’re not very common. Now that you know they exist, you will recognize them
in practice. In most cases, we can trust our “equality intuition” for both
Object.is(a, b) and a === b .
Loose Equality
Finally, we get to the last kind of equality.
Loose equality (double equals) is the bogeyman of JavaScript. Here are a couple
of examples to make your skin crawl:
Wait, what?!
The rules of loose equality (also called “abstract equality”) are arcane and
confusing. Many coding standards prohibit the use of == and != in code
altogether.
Although Just JavaScript doesn’t take strong opinions on what features you
should or shouldn’t use, we’re not going to cover loose equality in much detail. It’s
uncommon in modern codebases, and its rules don’t play a larger role in the
language—or in our mental model.
Fun Fact
The rules of loose equality are widely acknowledged as an early bad design
decision of JavaScript, but if you are still curious, you can check out how it
works here. Don’t feel pressured to memorize it—you’ll need that memory
for other topics!
if (x == null) {
// ...
}
However, even that usage of == might be controversial on some teams. It’s best
to discuss how much == is tolerated in your codebase as a team before using
it.
Recap
JavaScript has several kinds of equality. They include same value equality,
strict equality, and loose equality.
When we draw a diagram of values and variables, the same value cannot
appear twice. Object.is(a, b) is true when variables a and
b point to the same value on our diagram.
Same value equality is verbose and a bit annoying to write, but it's also
the easiest to explain, which is why we started with it.
NaN === NaN is false , even though they are the same value.
Exercises
Even though you’re likely familiar with the concept of equality, these exercises will
help you cement the mental model we’re building. We need this foundation before
we can get to more complex topics.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
08 Properties
let sherlock = {
surname: 'Holmes',
address: { city: 'London' }
};
let john = {
surname: 'Watson',
address: sherlock.address
};
Sherlock is a brilliant detective, but a difficult flatmate. One day, John decides he’s
had enough—he changes his surname and moves to Malibu:
john.surname = 'Lennon';
john.address.city = 'Malibu';
Time for a small exercise. Write down your answers to these questions:
console.log(sherlock.surname); // ?
console.log(sherlock.address.city); // ?
console.log(john.surname); // ?
console.log(john.address.city); // ?
Before re-reading the code, I want you to approach these questions in a particular
way. Use a sketchpad or get paper and a pen, and sketch out your mental model
of what happens on every line. It’s okay if you’re not sure how to represent it. We
haven’t yet discussed these topics, so use your best guess.
Then, with the help of your final sketch, answer the four questions above.
Answer
NowDon’t reveal your
let’s check until answers:
you have written the answers to four questions.
This is not a typo—they are indeed both in Malibu. It’s not so easy to get
Reveal
away from Sherlock! With an inaccurate mental model, one might
conclude that sherlock.address.city is "London" —but it’s not.
Properties
We’ve talked about objects before. For example, here is a sherlock variable
pointing to an object value. We create a new object value by writing {} :
let sherlock = {
surname: 'Holmes',
age: 64,
};
Here, sherlock is still a variable, but surname and age are not. They are
properties. Unlike variables, properties belong to a particular object.
In our JavaScript universe, both variables and properties act like “wires.”
However, the wires of properties start from objects rather than from our code:
Here, we can see that the sherlock variable points to an object we have
created. That object has two properties. Its surname property points to the
"Holmes" string value, and its age property points to the 64 number value.
Before reading this, you might have imagined that values live “inside” objects
because they appear “inside” in code. This intuition often leads to mistakes, so we
will be “thinking in wires” instead. Take one more look at the code and the
diagram. Make sure you’re comfortable with them before you continue.
Reading a Property
We can read a property’s current value by using the “dot notation”:
console.log(sherlock.age); // 64
0:00 / 0:06
It leads to an object. From that object, JavaScript follows the age property.
Property Names
One important thing to keep in mind when naming a property is that a single
object can’t have two properties with the same name. For example, our object
can’t have two properties called age .
Property names are also always case-sensitive! For example, age and Age
would be two completely different properties from JavaScript’s point of view.
If we don’t know a property name ahead of time, but we have it in code as a string
value, we can use the [] “bracket notation” to read it from an object:
Try this code in your browser console and enter age when prompted.
Assigning to a Property
What happens when we assign a value to a property?
sherlock.age = 65;
Let’s split this code into the left and the right side, separated by = .
We follow the sherlock wire, and then pick the age property wire:
0:00 / 0:05
Note that we don’t follow the age wire to 64 . We don’t care what its current
value is. On the left side of the assignment, we are looking for the wire itself.
Unlike the left side, the right side of an assignment always expresses a value. In
this example, the right side’s value is the number value 65 . Let’s summon it:
0:00 / 0:06
At last, we point the wire on the left side to the value on the right side:
0:00 / 0:06
And we’re done! From now on, reading sherlock.age would give us 65 .
Missing Properties
You might wonder what happens if we read a property that doesn’t exist:
These rules are a bit simplified, but they already tell us a lot about how JavaScript
works! For example, sherlock points to an object that doesn’t have a boat
property. So sherlock.boat gives us undefined as an answer:
Note this does not mean that our object has a boat property pointing to
undefined ! It only has two properties, and neither of them is called boat :
It looks at the object that sherlock points to, sees that it doesn’t have a
boat property, and gives us back the undefined value because that’s what
the rules say. There is no deeper reason for this: computers follow the rules.
Fun Fact
Fundamentally, it’s because every expression needs to result in some value,
or throw an error. Some other languages throw an error if you access a
property that doesn’t exist—but JavaScript is not one of them!
Scroll up and re-read the rules again. Can you apply them in practice?
Hint: there are two dots, so you need to follow the rules two times.
Answer
The answer is that calculating
Don’t reveal untilsherlock.boat.name throws an error:
you have finished writing.
value of sherlock.boat .
We need to first figure out theReveal
The rules say that null or undefined on the left side is an error.
If this still seems confusing, scroll up and mechanically follow the rules.
Recap
Properties are wires—a bit like variables. They both point to values. Unlike
variables, properties start from objects in our universe.
Properties belong to particular objects. You can’t have more than one
property with the same name on an object.
3. If that property exists, the result is the value it points to. If that property
doesn’t exist, the result is undefined .
Note that this mental model of properties is still a bit simplified. It is good enough
for now, but it will need to expand as you learn more about the JavaScript
universe.
If you got confused by the Sherlock Holmes example in the beginning, try walking
through it again using our new mental model and focusing on properties as wires.
The next module will include a detailed walkthrough in case you’re still not quite
sure why it works this way.
Exercises
Even though you’re likely familiar with the concept of properties and assignment,
these exercises will help you cement the mental model we’re building. We need
this foundation before we can get to more complex topics.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
09 Mutation
This time, we will walk through the code step-by-step and draw our diagrams
together so you can check your mental model.
let sherlock = {
surname: 'Holmes',
address: { city: 'London' }
};
Draw the diagram for this step now:
Answer
Your diagram Don’t
should end up
reveal looking
until like drawn
you have this: the diagram.
No Nested Objects
Notice that we have not one, but two completely separate objects here. Two pairs
of curly braces mean two objects.
Objects might appear “nested” in code, but in our universe, each object is
completely separate. An object cannot be “inside” of another object! If you have
been visualizing nested objects, now is the time to change your mental model.
let john = {
surname: 'Watson',
address: sherlock.address
};
This is misleading.
It’s the value itself (the object previously created with { city: 'London' } )
that matters during the assignment, not how we found it
( sherlock.address ).
As a result, there are now two different address properties pointing to the
same object. Can you spot them both on the diagram?
john.surname = 'Lennon';
john.address.city = 'Malibu';
0:00 / 0:22
We figure out the wire, then the value, and point the wire to that value.
The result should make sense now, but this example is confusing on a deeper
level. Where is the mistake in it? How do we actually fix the code so that John
moves to Malibu alone? To make sense of it, we need to talk about mutation.
Mutation
For example, we could say that we changed an object’s property, or we could say
that we mutated that object (and its property). This is the same thing.
People like to say “mutate” because this word has a sinister undertone. It reminds
you to exercise extra caution. This doesn’t mean mutation is “bad”—it’s just
programming!—but you need to be very intentional about it.
Let’s recall our original task. We wanted to give John a different surname, and
move him to Malibu. Now let’s look at our two mutations:
The first line mutates the object john points to. This makes sense: we mean to
change John’s surname. That object represents John’s data, so we mutate its
surname property.
However, the second line does something very different. It doesn’t mutate the
object that john points to. Rather, it mutates a completely different object—
the one we can reach via john.address . And if we look at the diagram, we
know we’ll reach the same object via sherlock.address !
Fun Fact
This is why the intuition of objects being “nested” is so wrong! It makes us
forget that there may be many objects pointing to the object we changed.
As you can see, visually similar code may produce very different results. Always
pay attention to the wires!
Alternative Solution: No Object Mutation
There is another way we can make john.address.city give us "Malibu"
while sherlock.address.city continues to say "London" :
Here, we don’t mutate John’s object at all. Instead, we reassign the john
variable to point to a “new version” of John’s data. From now on, john points to
a different object, whose address also points to a completely new object:
You might notice there’s now an “abandoned” old version of the John object on
our diagram. We don’t need to worry about it. JavaScript will eventually
automatically remove it from memory if there are no wires pointing to it.
console.log(sherlock.surname); // "Sherlock"
console.log(sherlock.address.city); // "London"
console.log(john.surname); // "Lennon"
console.log(john.address.city); // "Malibu"
Compare their diagrams. Do you have a personal preference for either of these
fixes? What are, in your opinion, their advantages and disadvantages?
2. Maybe the object we could reach via sherlock was mutated, and its
address property was set to something different.
3. Maybe the object we could reach via sherlock.address was mutated, and
its city property was set to something different.
Your mental model gives you a starting point from which you can investigate
bugs. This works the other way around too. Sometimes, you can tell a piece of
code is not the source of a problem—because the mental model proves it!
Say, if we point the john variable to a different object, we can be fairly sure that
sherlock.address.city won’t change. Our diagram shows that changing the
john wire doesn’t affect any wires coming from sherlock :
Still, keep in mind that unless you’re Sherlock Holmes, you can rarely be fully
confident in something. This approach is only as good as your mental model!
Mental models will help you come up with theories, but you need to confirm them
with console.log or a debugger.
But there’s a crucial nuance. We can still mutate the object our wire points to:
shrek.species = 'human';
console.log(shrek.species); // 'human'
In this example, it is only the shrek variable wire itself that is read-only
( const ). It points to an object—and that object’s properties can be mutated!
The usefulness of const is a hotly debated topic. Some prefer to ban let
altogether and always use const . Others say that programmers should be
trusted to reassign their own variables. Whatever your preference may be,
remember that const prevents variable reassignment—not object mutation.
Is Mutation Bad?
Don’t walk away thinking that mutation is “bad.” That’s a lazy oversimplification
that obscures real understanding. If data changes over time, a mutation happens
somewhere. The question is, what should be mutated, where, and when?
By the time you mutate an object, variables and properties may already be
pointing to it. Your mutation affects any code “following” those wires later.
This is both a blessing and a curse. Mutation makes it easy to change some data
and immediately “see” the change across the whole program. However,
undisciplined mutation makes it harder to predict what the program will do.
There is a school of thought that mutation is best contained to a very narrow
layer of your application. The benefit, according to this philosophy, is that your
program’s behavior is more predictable. The downside is that you write more code
to “pass things around” and avoid mutation.
It’s worth noting that mutating new objects that you’ve just created is always okay
because there are no other wires pointing to them yet. In other cases, I advise you
to be very intentional about what you’re mutating and when. The extent to which
you’ll rely on mutation depends on your app’s architecture.
Recap
Objects are never “nested” in our universe—pay close attention to the wires.
If you mutate an object, your code will “see” that change via any wires
pointing to that object. Sometimes, this may be what you want. However,
mutating accidentally shared data may cause bugs.
You can declare a variable with const instead of let . That allows you to
enforce that this variable always points to the same value. But remember that
const does not prevent object mutation!
Mutating the objects you’ve just created in code is safe. Broadly, how much
you’ll use mutation depends on your app’s architecture.
Exercises
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →
10
Prototypes
In previous modules, we’ve covered object properties and mutation, but we’re not
quite done—we still need to talk about prototypes!
We have just created an empty object with {} . We definitely didn’t set any
properties on it before logging, so it seems like pizza.taste can’t point to
"pineapple" . We would expect pizza.taste to give us undefined instead
—we usually get undefined when a property doesn’t exist, right?
And yet, it is possible that pizza.taste is "pineapple" ! This may be a
contrived example, but it shows that our mental model is incomplete.
Prototypes
let human = {
teeth: 32
};
let gwen = {
age: 19
};
console.log(gwen.teeth); // undefined
But the story doesn’t have to end here. JavaScript’s default behavior returns
undefined , but we can instruct it to continue searching for our missing property
on another object. We can do it with one line of code:
let human = {
teeth: 32
};
let gwen = {
// We added this line:
__proto__: human,
age: 19
};
Take a moment to verify the diagram matches the code. We drew it just like we did
in the past. The only new thing is the mysterious __proto__ wire.
Prototypes in Action
When we went looking for gwen.teeth , we got undefined because the
teeth property doesn’t exist on the object that gwen points to.
let human = {
teeth: 32
};
let gwen = {
// "Look for other properties here"
__proto__: human,
age: 19
};
console.log(gwen.teeth); // 32
0:00 / 0:11
No.
Yes, it points to 32 .
let human = {
teeth: 32
};
let gwen = {
__proto__: human,
age: 19
};
console.log(human.age); // ?
console.log(gwen.age); // ?
console.log(human.teeth); // ?
console.log(gwen.teeth); // ?
console.log(human.tail); // ?
console.log(gwen.tail); // ?
Answer
Now Don’t
let’s check
revealyour
untilanswers.
you have written the answers to six questions
This naturally leads to a question: but what if my object’s prototype has its own
prototype? And that prototype has its own prototype? Would that work?
let mammal = {
brainy: true,
};
let human = {
__proto__: mammal,
teeth: 32
};
let gwen = {
__proto__: human,
age: 19
};
console.log(gwen.brainy); // true
We can see that JavaScript will search for the property on our object, then on its
prototype, then on that object’s prototype, and so on. We would only get
undefined if we ran out of prototypes and still hadn’t found our property.
This is similar to saying, “I don’t know, but Alice might know.” Then Alice might
say, “Actually, I don’t know either—ask Bob.” Eventually, you will either arrive at
the answer or run out of people to ask!
Shadowing
Consider this slightly modified example:
let human = {
teeth: 32
};
let gwen = {
__proto__: human,
// This object has its own teeth property:
teeth: 31
};
Both objects define a property called teeth , so the results are different:
console.log(human.teeth); // 32
console.log(gwen.teeth); // 31
Note that gwen.teeth is 31 . If gwen didn’t have its own teeth property,
we would look at the prototype. But because the object that gwen points to has
its own teeth property, we don’t need to keep searching for the answer.
In other words, once we find our property, we stop the search.
If you ever want to check if an object has its own property wire with a certain
name, you can call a built-in function called hasOwnProperty . It returns true
for “own” properties, and does not look at the prototypes. In our last example,
both objects have their own teeth wires, so it is true for both:
console.log(human.hasOwnProperty('teeth')); // true
console.log(gwen.hasOwnProperty('teeth')); // true
Assignment
Consider this example:
let human = {
teeth: 32
};
let gwen = {
__proto__: human,
// Note: no own teeth property
};
gwen.teeth = 31;
console.log(human.teeth); // ?
console.log(gwen.teeth); // ?
gwen.teeth = 31;
Now the question is which wire does gwen.teeth correspond to? The answer
is that, generally saying, assignments happen on the object itself.
console.log(human.teeth); // 32
console.log(gwen.teeth); // 31
When we read a property that doesn’t exist on our object, we’ll keep looking for it
on the prototype chain. If we don’t find it, we get undefined .
But when we write a property that doesn’t exist on our object, that creates the
property on our object. Generally speaking, prototypes will not play a role.
At first, this might be a bit mind-blowing. Let that sink in. All this time, we were
thinking {} created an “empty” object, but it’s not so empty after all! It has a
hidden __proto__ wire that points to the Object Prototype by default.
let human = {
teeth: 32
};
console.log(human.hasOwnProperty); // (function)
console.log(human.toString); // // (function)
These “built-in” properties are nothing more than normal properties on the Object
Prototype. Because our object’s prototype is the Object Prototype, we can access
them.
let weirdo = {
__proto__: null
};
The answer is yes—this will produce an object that truly doesn’t have a prototype
at all. As a result, it doesn’t even have built-in object methods:
console.log(weirdo.hasOwnProperty); // undefined
console.log(weirdo.toString); // undefined
You probably won’t want to create objects like this, however the Object Prototype
is exactly that—an object with no prototype.
console.log(sherlock.smell); // "banana"
console.log(watson.smell); // "banana"
In the past, prototype pollution was a popular way to extend JavaScript with
custom features. However, over the years, the web community realized that it is
fragile and makes it hard to add new language features, so we prefer to avoid it.
Now you can solve the pineapple pizza puzzle from the beginning of this module!
Check your solution in your console.
Fun Fact
__proto__ vs. prototype
You might be wondering: what in the world is the prototype property?
You might have seen it in MDN documentation.
The story around this is confusing. Before JavaScript added classes, it was
common to write them as functions that produce objects, for example:
function Donut() {
return { shape: 'round' };
}
You’d want all donuts to share a prototype with some shared methods.
However, manually adding __proto__ to every object looks gross:
function Donut() {
return { shape: 'round' };
}
let donutProto = {
eat() {
console.log('Nom nom nom');
}
};
donut1.eat();
donut2.eat();
donut1.eat();
donut2.eat();
Now this pattern has mostly fallen into obscurity, but you can still see
prototype property on the built-in functions and even on classes. To
conclude, a function’s prototype specifies the __proto__ of the
objects created by calling that function with a new keyword.
Personally, I don’t use a lot of classes in my daily coding, and I rarely deal with
prototypes directly either. However, it helps to know how those features build on
each other, and it’s important to know what happens when I read or set a property
on an object.
Recap
You probably won’t use prototypes much directly in practice. However, they
are fundamental to JavaScript objects, so it is handy to understand their
underlying mechanics. Some advanced JavaScript features, including classes,
can be expressed in terms of prototypes.
Exercises
Even though you’re likely familiar with the concept of prototypes, these exercises
will help you cement the mental model we’re building. We need this foundation
before we can get to more complex topics.
Finished reading?
Mark this episode as learned to track your progress.
Mark as learned
UP NEXT
Take a Quiz →