During my first few years of using JavaScript, I felt like a fraud. Even though I could build websites with frameworks, something was missing. I dreaded JavaScript job interviews because I didn’t have a solid grasp on fundamentals.
Over the years, I’ve formed a mental model of JavaScript that gave me confidence. Here, I’m sharing a very compressed version of it. It’s structured like a glossary, with each topic getting a few sentences.
As you read through this post, try to mentally keep score about how confident you feel about each topic. I won’t judge you if quite a few of them are a miss! At the end of this post, there is something that might help in that case.
Value: The concept of a value is a bit abstract. It’s a “thing”. A value to JavaScript is what a number is to math, or what a point is to geometry. When your program runs, its world is full of values. Numbers like
1
,2
, and420
are values, but so are some other things, like this sentence:"Cows go moo"
. Not everything is a value though. A number is a value, but anif
statement is not. We’ll look at a few different kinds of values below.Type of Value: There are a few different “types” of values. For example, numbers like
420
, strings like"Cows go moo"
, objects, and a few other types. You can learn a type of some value by puttingtypeof
before it. For example,console.log(typeof 2)
prints"number"
.Primitive Values: Some value types are “primitive”. They include numbers, strings, and a few other types. One peculiar thing about primitive values is that you can’t create more of them, or change them in any way. For example, every time you write
2
, you get the same value2
. You can’t “create” another2
in your program, or make the2
value “become”3
. This is also true for strings.null
andundefined
: These are two special values. They’re special because there’s a lot of things you can’t do with them — they often cause errors. Usually,null
represents that some value is missing intentionally, andundefined
represents that a value is missing unintentionally. However, when to use either is left to the programmer. They exist because sometimes it’s better for an operation to fail than to proceed with a missing value.
Equality: Like “value”, equality is a fundamental concept in JavaScript. We say two values are equal when they’re… actually, I’d never say that. If two values are equal, it means they are the same value. Not two different values, but one! For example,
"Cows go moo" === "Cows go moo"
and2 === 2
because2
is2
. Note we use three equal signs to represent this concept of equality in JavaScript.Strict Equality: Same as above.
Referential Equality: Same as above.
Loose Equality: Oof, this one is different! Loose equality is when we use two equal signs (
==
). Things may be considered loosely equal even if they refer to different values that look similar (such as2
and"2"
). It was added to JavaScript early on for convenience and has caused endless confusion ever since. This concept is not fundamental, but is a common source of mistakes. You can learn how it works on a rainy day, but many people try to avoid it.
Literal: A literal is when you refer to a value by literally writing it down in your program. For example,
2
is a number literal, and"Banana"
is a string literal.Variable: A variable lets you refer to some value using a name. For example,
let message = "Cows go moo"
. Now you can writemessage
instead of repeating the same sentence every time in your code. You may later changemessage
to point to another value, likemessage = "I am the walrus"
. Note this doesn’t change the value itself, but only where themessage
points to, like a “wire”. It pointed to"Cows go moo"
, and now it points to"I am the walrus"
.Scope: It would suck if there could only be one
message
variable in the whole program. Instead, when you define a variable, it becomes available in a part of your program. That part is called a “scope”. There are rules about how scope works, but usually you can search for the closest{
and}
braces around where you define the variable. That “block” of code is its scope.Assignment: When we write
message = "I am the walrus"
, we change themessage
variable to point to"I am the walrus"
value. This is called an assignment, writing, or setting the variable.let
vsconst
vsvar
: Usually you wantlet
. If you want to forbid assignment to this variable, you can useconst
. (Some codebases and coworkers are pedantic and force you to useconst
when there is only one assignment.) Avoidvar
if you can because its scoping rules are confusing.
Object: An object is a special kind of value in JavaScript. The cool thing about objects is that they can have connections to other values. For example, a
{flavor: "vanilla"}
object has aflavor
property that points to the"vanilla"
value. Think of an object as “your own” value with “wires” from it.Property: A property is like a “wire” sticking from an object and pointing to some value. It might remind you of a variable: it has a name (like
flavor
) and points to a value (like"vanilla"
). But unlike a variable, a property “lives” in the object itself rather than in some place in your code (scope). A property is considered a part of the object — but the value it points to is not.Object Literal: An object literal is a way to create an object value by literally writing it down in your program, like
{}
or{flavor: "vanilla"}
. Inside{}
, we can have multipleproperty: value
pairs separated by commas. This lets us set up where the property “wires” point to from our object.Object Identity: We mentioned earlier that
2
is equal to2
(in other words,2 === 2
) because whenever we write2
, we “summon” the same value. But whenever we write{}
, we will always get a different value! So{}
is not equal to another{}
. Try this in console:{} === {}
(the result is false). When the computer meets2
in our code, it always gives us the same2
value. However, object literals are different: when a computer meets{}
, it creates a new object, which is always a new value. So what is object identity? It’s yet another term for equality, or same-ness of values. When we say “a
andb
have the same identity”, we mean “a
andb
point to the same value” (a === b
). When we say “a
andb
have different identities”, we mean “a
andb
point to different values” (a !== b
).Dot Notation: When you want to read a property from an object or assign to it, you can use the dot (
.
) notation. For example, if a variableiceCream
points to an object whose propertyflavor
points to"chocolate"
, writingiceCream.flavor
will give you"chocolate"
.Bracket Notation: Sometimes you don’t know the name of the property you want to read in advance. For example, maybe sometimes you want to read
iceCream.flavor
and sometimes you want to readiceCream.taste
. The bracket ([]
) notation lets you read the property when its name itself is a variable. For example, let’s say thatlet ourProperty = 'flavor'
. TheniceCream[ourProperty]
will give us"chocolate"
. Curiously, we can use it when creating objects too:{ [ourProperty]: "vanilla" }
.Mutation: We say an object is mutated when somebody changes its property to point to a different value. For example, if we declare
let iceCream = {flavor: "vanilla"}
, we can later mutate it withiceCream.flavor = "chocolate"
. Note that even if we usedconst
to declareiceCream
, we could still mutateiceCream.flavor
. This is becauseconst
would only prevent assignments to theiceCream
variable itself, but we mutated a property (flavor
) of the object it pointed to. Some people swore off usingconst
altogether because they find this too misleading.Array: An array is an object that represents a list of stuff. When you write an array literal like
["banana", "chocolate", "vanilla"]
, you essentially create an object whose property called0
points to the"banana"
string value, property called1
points to the"chocolate"
value, and property called2
points to the"vanilla"
value. It would be annoying to write{0: ..., 1: ..., 2: ...}
which is why arrays are useful. There are also some built-in ways to operate on arrays, likemap
,filter
, andreduce
. Don’t despair ifreduce
seems confusing — it’s confusing to everyone.Prototype: What happens if we read a property that doesn’t exist? For example,
iceCream.taste
(but our property is calledflavor
). The simple answer is we’ll get the specialundefined
value. The more nuanced answer is that most objects in JavaScript have a “prototype”. You can think of a prototype as a “hidden” property on every object that determines “where to look next”. So if there’s notaste
property oniceCream
, JavaScript will look for ataste
property on its prototype, then on that object’s prototype, and so on, and will only give usundefined
if it reaches the end of this “prototype chain” without finding.taste
. You will rarely interact with this mechanism directly, but it explains why ouriceCream
object has atoString
method that we never defined — it comes from the prototype.
Function: A function is a special value with one purpose: it represents some code in your program. Functions are handy if you don’t want to write the same code many times. “Calling” a function like
sayHi()
tells the computer to run the code inside it and then go back to where it was in the program. There are many ways to define a function in JavaScript, with slight differences in what they do.Arguments (or Parameters): Arguments let you pass some information to your function from the place you call it:
sayHi("Amelie")
. Inside the function, they act similar to variables. They’re called either “arguments” or “parameters” depending on which side you’re reading (function definition or function call). However, this distinction in terminology is pedantic, and in practice these two terms are used interchangeably.Function Expression: Previously, we set a variable to a string value, like
let message = "I am the walrus"
. It turns out that we can also set a variable to a function, likelet sayHi = function() { }
. The thing after=
here is called a function expression. It gives us a special value (a function) that represents our piece of code, so we can call it later if we want to.Function Declaration: It gets tiring to write something like
let sayHi = function() { }
every time, so we can use a shorter form instead:function sayHi() { }
. This is called a function declaration. Instead of specifying the variable name on the left, we put it after thefunction
keyword. These two styles are mostly interchangeable.Function Hoisting: Normally, you can only use a variable after its declaration with
let
orconst
has run. This can be annoying with functions because they may need to call each other, and it’s hard to track which function is used by which others and needs to be defined first. As a convenience, when (and only when!) you use the function declaration syntax, the order of their definitions doesn’t matter because they get “hoisted”. This is a fancy way of saying that conceptually, they all automatically get moved to the top of the scope. By the time you call them, they’re all defined.this
: Probably the most misunderstood JavaScript concept,this
is like a special argument to a function. You don’t pass it to a function yourself. Instead, JavaScript itself passes it, depending on how you call the function. For example, calls using the dot.
notation — likeiceCream.eat()
— will get a specialthis
value from whatever is before the.
(in our example,iceCream
). The value ofthis
inside a function depends on how the function is called, not where it’s defined. Helpers like.bind
,.call
, and.apply
let you have for more control over the value ofthis
.Arrow Functions: Arrow functions are similar to function expressions. You declare them like this:
let sayHi = () => { }
. They’re concise and are often used for one-liners. Arrow functions are more limited than regular functions — for example, they have no concept ofthis
whatsoever. When you writethis
inside of an arrow function, it usesthis
of the closest “regular” function above. This is similar to what would happen if you used an argument or a variable that only exists in a function above. Practically, this means that people use arrow functions when they want to “see” the samethis
inside of them as in the code surrounding them.Function Binding: Usually, binding a function
f
to a particularthis
value and arguments means creating a new function that callsf
with those predefined values. JavaScript has a built-in helper to do it called.bind
, but you could also do it by hand. Binding was a popular way to make nested functions “see” the same value ofthis
as the outer functions. But now this use case is handled by arrow functions, so binding is not used as often.Call Stack: Calling a function is like entering a room. Every time we call a function, the variables inside of it are initialized all over again. So each function call is like constructing a new “room” with its code and entering it. Our function’s variables “live” in that room. When we return from the function, that “room” disappears with all its variables. You can visualize these rooms as a vertical stack of rooms — a call stack. When we exit a function, we go back to the function “below” it on the call stack.
Recursion: Recursion means that a function calls itself from within itself. This is useful for when you want to repeat the thing you just did in your function again, but for different arguments. For example, if you’re writing a search engine that crawls the web, your
collectLinks(url)
function might first collect the links from a page, and then call itself for every link until it visits all pages. The pitfall with recursion is that it’s easy to write code that never finishes because a function keeps calling itself forever. If this happens, JavaScript will stop it with an error called “stack overflow”. It’s called this way because it means we have too many function calls stacked in our call stack, and it has literally overflown.Higher-Order Function: A higher-order function is a function that deals with other functions by taking them as arguments or returning them. This might seem weird at first, but we should remember that functions are values so we can pass them around — like we do with numbers, strings, or objects. This style can be overused, but it’s very expressive in moderation.
Callback: A callback is not really a JavaScript term. It’s more of a pattern. It’s when you pass a function as an argument to another function, expecting it to call your function back later. You’re expecting a “call back”. For example,
setTimeout
takes a callback function and… calls you back after a timeout. But there’s nothing special about callback functions. They’re regular functions, and when we say “callback” we only talk about our expectations.Closure: Normally, when you exit a function, all its variables “disappear”. This is because nothing needs them anymore. But what if you declare a function inside a function? Then the inner function could still be called later, and read the variables of the outer function. In practice, this is very useful! But for this to work, the outer function’s variables need to “stick around” somewhere. So in this case, JavaScript takes care of “keeping the variables alive” instead of “forgetting” them as it would usually do. This is called a “closure”. While closures are often considered a misunderstood JavaScript aspect, you probably use them many times a day without realizing it!
JavaScript is made of these concepts, and more. I felt very anxious about my knowledge of JavaScript until I could build a correct mental model, and I’d like to help the next generation of developers bridge this gap sooner.
If you want to join me for a deeper dive in each of these topics, I have something for you. Just JavaScript is my distilled mental model of how JavaScript works, and it’s going to feature visual illustrations by the amazing Maggie Appleton. Unlike this post, it goes at a slower pace so you can follow along on every detail.