The JS Handbook
The JS Handbook
The JS Handbook
com
The JS Handbook
Written by Flavio Copes. Updated August 2022
1. Introduction to JavaScript
2. A little bit of history
3. Just JavaScript
4. A brief intro to the syntax of JavaScript
4.1. White space
4.2. Case sensitive
4.3. Literals
4.4. Identifiers
4.5. Comments
5. Semicolons
6. Values
7. Variables
8. Types
8.1. Primitive types
9. Expressions
10. Operators
10.1. The addition operator (+)
13. Conditionals
13.1. Else
14. Arrays
14.1. How to add an item to an array
14.2. How to remove an item from an array
15. Strings
16. Loops
16.1. while
16.2. for
16.3. for...of
17. Functions
18. Arrow functions
19. Objects
19.1. Object Properties
20. Classes
21. Inheritance
23. Promises
websites
web applications
but JavaScript is not limited to these things, and it can also be used to
It can basically do anything. It’s so popular that everything new that shows up is
going to have some kind of JavaScript integration at some point.
high level: it provides abstractions that allow you to ignore the details of the
machine where it’s running on. It manages memory automatically with a garbage
collector, so you can focus on the code instead of managing memory like other
languages like C would need, and provides many constructs which allow you to deal
with highly powerful variables and objects.
loosely typed: as opposed to strong typing, loosely (or weakly) typed languages
do not enforce the type of an object, allowing more flexibility but denying us type
safety and type checking (something that TypeScript - which builds on top of
JavaScript - provides)
In case you’re wondering, JavaScript has nothing to do with Java, it’s a poor name
choice but we have to live with it.
It was the first scripting language that was supported natively by web browsers, and
thanks to this it gained a competitive advantage over any other language and today
it’s still the only scripting language that we can use to build Web Applications.
Other languages exist, but all must compile to JavaScript - or more recently to
WebAssembly, but this is another story.
In the beginning, JavaScript was not nearly powerful as it is today, and it was mainly
used for fancy animations and the marvel known at the time as Dynamic HTML.
With the growing needs that the web platform demanded (and continues to
demand), JavaScript had the responsibility to grow as well, to accommodate the
needs of one of the most widely used ecosystems of the world.
JavaScript is now widely used also outside of the browser. The rise of Node.js in the
last few years unlocked backend development, once the domain of Java, Ruby,
Python, PHP and more traditional server-side languages.
JavaScript is now also the language powering databases and many more
applications, and it’s even possible to develop embedded applications, mobile apps,
TV sets apps and much more. What started as a tiny language inside the browser is
now the most popular language in the world.
3. Just JavaScript
Sometimes it’s hard to separate JavaScript from the features of the environment it is
used in.
For example, the console.log() line you can find in many code examples is not
JavaScript. Instead, it’s part of the vast library of APIs provided to us in the browser.
In the same way, on the server, it can be sometimes hard to separate the JavaScript
language features from the APIs provided by Node.js.
In this book I talk about JavaScript, the language, without complicating your learning
process with things that are outside of it, and provided by external ecosystems.
white space
case sensitivity
literals
identifiers
comments
JavaScript does not consider white space meaningful. Spaces and line breaks can be
added in any fashion you might like, even though this is in theory.
In practice, you will most likely keep a well-defined style and adhere to what people
commonly use, and enforce this using a linter or a style tool such as Prettier.
For example, I like to always use 2 characters to indent.
4.3. Literals
We define as literal a value that is written in the source code, for example, a
number, a string, a boolean or also more advanced constructs, like Object Literals or
Array Literals:
5
'Test'
true
['a', 'b']
{color: 'red', shape: 'Rectangle'}
4.4. Identifiers
Test
test
TEST
_test
Test1
$test
Some names are reserved for JavaScript internal use, and we can’t use them as
identifiers.
4.5. Comments
Comments are one of the most important parts of any program. In any
programming language. They are important because they let us annotate the code
and add important information that otherwise would not be available to other
people (or ourselves) reading the code.
Like this:
// a comment
true //another comment
Another type of comment is a multi-line comment. It starts with /* and ends with
*/ .
/* some kind
of
comment
*/
5. Semicolons
Every line in a JavaScript program is optionally terminated using semicolons.
In most cases, you can omit semicolons altogether from your programs.
This fact is very controversial, and you’ll always find code that uses semicolons and
code that does not.
6. Values
A hello string is a value. A number like 12 is a value.
hello and 12 are values. string and number are the types of those values.
The type is the kind of value, its category. We have many different types in
JavaScript, and we’ll talk about them in detail later on. Each type has its own
characteristics.
7. Variables
A variable is a value assigned to an identifier, so you can reference and use it later in
the program.
This is because JavaScript is loosely typed, a concept you’ll frequently hear about.
const a = 0
let a = 0
const defines a constant reference to a value. This means the reference cannot be
changed. You cannot reassign a new value to it.
const a = 0
a = 1
let a = 0
a = 1
const does not mean “constant” in the way some other languages like C mean. In
particular, it does not mean the value cannot change - it means it cannot be
reassigned. If the variable points to an object or an array (we’ll see more about
objects and arrays later) the content of the object or the array can freely change.
const a = 0
let a
a = 0
const a = 1,
b = 2
let c = 1,
d = 2
But you cannot redeclare the same variable more than one time:
let a = 1
let a = 2
My advice is to always use const and only use let when you know you’ll need to
reassign a value to that variable. Why? Because the less power our code has, the
better. If we know a value cannot be reassigned, it’s one less source for bugs.
Now that we saw how to work with const and let , I want to mention var .
Until 2015, var was the only way we could declare a variable in JavaScript. Today, a
modern codebase will most likely just use const and let . There are some
fundamental differences which I detail in this post but if you’re just starting out, you
might not care about them. Just use const and let .
8. Types
Variables in JavaScript do not have any type attached.
Once you assign a value with some type to a variable, you can later reassign the
variable to host a value of any other type, without any issue.
In JavaScript we have 2 main kinds of types: primitive types and object types.
numbers
strings
booleans
symbols
Any value that’s not of a primitive type (a string, a number, a boolean, null or
undefined) is an object.
Object types have properties and also have methods that can act on those
properties.
9. Expressions
An expression is a single unit of JavaScript code that the JavaScript engine can
evaluate, and return a value.
2
0.02
;('something')
true
false
this //the current scope
undefined
i //where i is a variable or a constant
Arithmetic expressions are expressions that take a variable and an operator (more
on operators soon), and result into a number:
1 / 2
i++
i -= 2
i * 2
Logical expressions make use of logical operators and resolve to a boolean value:
a && b
a || b
!a
More advanced expressions involve objects, functions, and arrays, and I’ll introduce
them later.
10. Operators
Operators allow you to get two simple expressions and combine them to form a
more complex expression.
We can classify operators based on the operands they work with. Some operators
work with 1 operand. Most with 2 operands. Just one operator works with 3
operands.
In this first introduction to operators, we’ll introduce the operators you are most
likely familiar with: binary operators.
I already introduced one when talking about variables: the assignment operator = .
You use = to assign a value to a variable:
let b = 2
Let’s now introduce another set of binary operators that you are already familiar
with, from basic math.
const three = 1 + 2
const four = three + 1
The + operator also serves as string concatenation if you use strings, so pay
attention:
const three = 1 + 2
three + 1 // 4
'three' + 1 // three1
const two = 4 - 2
If you divide by zero, JavaScript does not raise any error but returns the Infinity
1 / 0 - //Infinity
1 / 0 //-Infinity
10.4. The remainder operator (%)
A remainder by zero is always NaN , a special value that means “Not a Number”:
1 % 0 //NaN
1 * 2 //2
1 * -2 //-2
1 ** 2 //1
2 ** 1 //2
2 ** 2 //4
2 ** 8 //256
8 ** 2 //64
let a = 1 * 2 + ((5 / 2) % 2)
Operations on the same level (like + and - ) are executed in the order they are
found, from left to right.
Following these rules, the operation above can be solved in this way:
let a = 1 * 2 + ((5 / 2) % 2)
let a = 2 + ((5 / 2) % 2)
let a = 2 + (2.5 % 2)
let a = 2 + 0.5
let a = 2.5
You can use the following operators to compare two numbers, or two strings.
Example:
let a = 2
a >= 1 //true
In addition to those, we have 4 equality operators. They accept two values, and
return a boolean:
Note that we also have == and != in JavaScript, but I highly suggest to only use ===
13. Conditionals
With the comparison operators in place, we can talk about conditionals.
if (true) {
//do something
}
if (false) {
//do something (? never ?)
}
The conditional checks the expression you pass to it for a true or false value. If you
pass a number, that always evaluates to true unless it’s 0. If you pass a string, it
always evaluates to true unless it’s an empty string. Those are general rules of
casting types to a boolean.
Did you notice the curly braces? That is called a block, and it is used to group a list
of different statements.
A block can be put wherever you can have a single statement. And if you have a
single statement to execute after the conditionals, you can omit the block, and just
write the statement:
if (true) doSomething()
13.1. Else
if (true) {
//do something
} else {
//do something else
}
Since else accepts a statement, you can nest another if/else statement inside it:
if (a === true) {
//do something
} else if (b === true) {
//do something else
} else {
//fallback
}
14. Arrays
An array is a collection of elements.
const a = []
const a = Array()
The first is using the array literal syntax. The second uses the Array built-in
function.
You can pre-fill the array using this syntax:
const a = [1, 2, 3]
const a = Array.of(1, 2, 3)
Since we can add an array into an array, we can create multi-dimensional arrays,
which have very useful applications (e.g. a matrix):
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
matrix[0][0] //1
matrix[2][0] //7
You can access any element of the array by referencing its index, which starts from
zero:
a[0] //1
a[1] //2
a[2] //3
You can initialize a new array with a set of values using this syntax, which first
initializes an array of 12 elements, and fills each element with the 0 number:
Array(12).fill(0)
You can get the number of elements in the array by checking its length property:
const a = [1, 2, 3]
a.length //3
Note that you can set the length of the array. If you assign a bigger number than the
array’s current capacity, nothing happens. If you assign a smaller number, the array
is cut at that position:
const a = [1, 2, 3]
a //[ 1, 2, 3 ]
a.length = 2
a //[ 1, 2 ]
We can add an element at the end of an array using the push() method:
a.push(4)
We can add an element at the beginning of an array using the unshift() method:
a.unshift(0)
a.unshift(-2, -1)
We can remove an item from the end of an array using the pop() method:
a.pop()
We can remove an item from the beginning of an array using the shift() method:
a.shift()
const a = [1, 2]
const b = [3, 4]
const c = a.concat(b) //[1,2,3,4]
a //[1,2]
b //[3,4]
You can also use the spread operator ( ... ) in this way:
const a = [1, 2]
const b = [3, 4]
const c = [...a, ...b]
c //[1,2,3,4]
This method returns the first item that returns true in the callback function
provided. It returns undefined if nothing returns “true”.
It’s your responsibility to define the body of the callback function, so you can tell
find() what you’re looking for.
const my_id = 3
The above line will return the first element in the array that has id equal to 3 , the
value of my_id .
findIndex() is another array method that works similarly to find() , but returns the
index of the first item that returns true, and if not found, it returns undefined :
a.includes(value)
15. Strings
A string is a sequence of characters.
'A string'
'Another string'
I personally prefer single quotes all the time, and use double quotes only in HTML to
define attributes.
You can determine the length of a string using the length property of it:
'Flavio'.length //6
const name = 'Flavio'
name.length //6
''.length //0
Once a template literal is opened with the backtick, you just press enter to create a
new line, with no special characters, and it’s rendered as-is:
string
is awesome!`
Template literals are also great because they provide an easy way to interpolate
variables and expressions into strings.
16. Loops
Loops are one of the main control structures of JavaScript.
With a loop we can automate and repeat indefinitely a block of code, for how many
times we want it to run.
while loops
for loops
for..of loops
16.1. while
The while loop is the simplest looping structure that JavaScript provides us.
We add a condition after the while keyword, and we provide a block that is run until
the condition evaluates to true .
Example:
You can interrupt a while loop using the break keyword, like this:
while (true) {
if (somethingIsTrue) break
}
and if you decide that in the middle of a loop you want to skip the current iteration,
you can jump to the next iteration using continue :
while (true) {
if (somethingIsTrue) continue
Very similar to while , we have do..while loops. It’s basically the same as while ,
Example:
const list = ['a', 'b', 'c']
let i = 0
do {
console.log(list[i]) //value
console.log(i) //index
i = i + 1
} while (i < list.length)
16.2. for
The second very important looping structure in JavaScript is the for loop.
We use the for keyword and we pass a set of 3 instructions: the initialization, the
condition, and the increment part.
Example:
Just like with while loops, you can interrupt a for loop using break and you can fast
forward to the next iteration of a for loop using continue .
16.3. for...of
This loop is relatively recent (introduced in 2015) and it’s a simplified version of the
for loop:
17. Functions
In any moderately complex JavaScript program, everything happens inside functions.
What is a function?
function getData() {
// do something
}
A function can be run any time you want by invoking it, like this:
getData()
function getData() {
//do something
}
function getData(color) {
//do something
}
getData('green', 24)
getData('black')
Note that in the second invocation I passed the black string parameter as the color
argument, but no age . In this case, age inside the function is undefined .
although in this case the conditional will be false if age is null , 0 or an empty string.
You can have default values for parameters, in case they are not passed:
You can pass any value as a parameter: numbers, strings, booleans, arrays, objects,
and also functions.
A function has a return value. By default a function returns undefined , unless you add
a return keyword with a value:
function getData() {
// do something
return 'hi!'
}
We can assign this return value to a variable when we invoke the function:
function getData() {
// do something
return 'hi!'
}
To return multiple values, you can return an object, or an array, like this:
function getData() {
return ['Flavio', 37]
}
The nested function cannot be called from the outside of the enclosing function.
They are very often used instead of “regular” functions, the one I described in the
previous chapter. You’ll find both forms used everywhere.
Visually, they allow you to write functions with a shorter syntax, from:
function getData() {
//...
}
to
;() => {
//...
}
If the function body contains just a single statement, you can omit the parentheses
and write it all on a single line:
If you have one (and just one) parameter, you could omit the parentheses
completely:
Arrow functions allow you to have an implicit return: values are returned without
having to use the return keyword.
getData() //'test'
You can have default values for parameters, in case they are not passed:
Arrow functions can contain other arrow function, or also regular functions.
They are very similar, so you might ask why they were introduced? The big
difference with regular functions is when they are used as object methods. This is
something we’ll soon look into.
19. Objects
Any value that’s not of a primitive type (a string, a number, a boolean, a symbol, null,
or undefined) is an object.
const car = {}
This is the object literal syntax, which is one of the nicest things in JavaScript.
You can also initialize an object using the new keyword before a function with a
capital letter. This function serves as a constructor for that object. In there, we can
initialize the arguments we receive as parameters, to set up the initial state of the
object:
If you assign a variable the same value of another, if it’s a primitive type like a
number or a string, they are passed by value:
const car = {
color: 'blue',
}
const anotherCar = car
anotherCar.color = 'yellow'
car.color //'yellow'
Even arrays or functions are, under the hood, objects, so it’s very important to
understand how they work.
Objects have properties, which are composed by a label associated with a value.
The value of a property can be of any type, which means that it can be an array, a
function, and it can even be an object, as objects can nest other objects.
const car = {}
const car = {
color: 'blue',
}
here we have a car object with a property named color , with the value blue .
Labels can be any string, but beware special characters: if I wanted to include a
character not valid as a variable name in the property name, I would have had to use
quotes around it:
const car = {
color: 'blue',
'the color': 'blue',
}
Invalid variable name characters include spaces, hyphens, and other special
characters.
As you see, when we have multiple properties, we separate each property with a
comma.
car.color //'blue'
The second (which is the only one we can use for properties with invalid names), is
to use square brackets:
car.brand //undefined
const car = {
brand: {
name: 'Ford',
},
color: 'blue',
}
car.brand.name
or
car['brand']['name']
You can set the value of a property when you define the object.
const car = {
color: 'blue',
}
car.color = 'yellow'
car['color'] = 'red'
car.model = 'Fiesta'
car.model //'Fiesta'
const car = {
color: 'blue',
brand: 'Ford',
}
delete car.brand
Functions can be assigned to a function property, and in this case they are called
methods.
In this example, the start property has a function assigned, and we can invoke it by
using the dot syntax we used for properties, with the parentheses at the end:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function () {
console.log('Started')
},
}
car.start()
Inside a method defined using a function() {} syntax we have access to the object
instance by referencing this .
In the following example, we have access to the brand and model properties values
using this.brand and this.model :
const car = {
brand: 'Ford',
model: 'Fiesta',
start: function () {
console.log(`Started
${this.brand} ${this.model}`)
},
}
car.start()
It’s important to note this distinction between regular functions and arrow functions:
we don’t have access to this if we use an arrow function:
const car = {
brand: 'Ford',
model: 'Fiesta',
start: () => {
console.log(`Started
${this.brand} ${this.model}`) //not going to work
},
}
car.start()
This is the reason why regular functions are often used as object methods.
Methods can accept parameters, like regular functions:
const car = {
brand: 'Ford',
model: 'Fiesta',
goTo: function (destination) {
console.log(`Going to ${destination}`)
},
}
car.goTo('Rome')
20. Classes
We talked about objects, which are one of the most interesting parts of JavaScript.
What are classes? They are a way to define a common pattern for multiple objects.
const person = {
name: 'Flavio',
}
We can create a class named Person (note the capital P , a convention when using
classes), that has a name property:
class Person {
name
}
flavio.name
class Person {
hello() {
return 'Hello, I am Flavio'
}
}
class Person {
hello() {
return 'Hello, I am Flavio'
}
}
const flavio = new Person()
flavio.hello()
There is a special method called constructor() that we can use to initialize the class
properties when we create a new object instance.
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
Now we can instantiate a new object from the class, passing a string, and when we
call hello , we’ll get a personalized message:
When the object is initialized, the constructor method is called, with any parameters
passed.
Normally methods are defined on the object instance, not on the class.
You can define a method as static to allow it to be executed on the class instead:
class Person {
static genericHello() {
return 'Hello'
}
}
Person.genericHello() //Hello
21. Inheritance
A class can extend another class, and objects initialized using that class inherit all
the methods of both classes.
class Person {
hello() {
return 'Hello, I am a Person'
}
}
We can define a new class Programmer that extends Person :
Now if we instantiate a new object with class Programmer , it has access to the hello()
method:
Inside a child class, you can reference the parent class calling super() :
This means that a line of code is executed, then the next one is executed, and so on.
However, there are times when you cannot just wait for a line of code to execute.
You can’t just wait 2 seconds for a big file to load, and halt the program completely.
You can’t just wait for a network resource to be downloaded, before doing
something else.
One of the simplest examples of how to use callbacks is timers. Timers are not part
of JavaScript, but they are provided by the browser, and Node.js. Let me talk about
one of the timers we have: setTimeout() .
Example:
setTimeout(() => {
// runs after 2 seconds
console.log('inside the function')
}, 2000)
The function containing the console.log('inside the function') line will be executed
after 2 seconds.
after it:
console.log('before')
setTimeout(() => {
// runs after 2 seconds
console.log('inside the function')
}, 2000)
console.log('after')
before
after
inside the function
This is a very common pattern when working with the file system, the network,
events, or the DOM in the browser.
All of the things I mentioned are not “core” JavaScript, so they are not explained in
this handbook, but you’ll find lots of examples in my other handbooks available at
flaviocopes.com.
When the code is ready to invoke the callback, we invoke it passing the result:
doSomething((result) => {
console.log(result)
})
23. Promises
Promises are an alternative way to deal with asynchronous code.
Like this:
doSomething((result) => {
console.log(result)
})
When the doSomething() code ends, it calls the function received as a a parameter:
The main problem with this approach is that if we need to use the result of this
function in the rest of our code, all our code must be nested inside the callback, and
if we have to do 2-3 callbacks we enter in what is usually defined “callback hell” with
many levels of functions indented into other functions:
doSomething((result) => {
doSomethingElse((anotherResult) => {
doSomethingElseAgain((yetAnotherResult) => {
console.log(result)
})
})
})
Instead of doing:
doSomething((result) => {
console.log(result)
})
doSomething().then((result) => {
console.log(result)
})
We first call the function, then we have a then() method that is called when the
function ends.
The indentation does not matter, but you’ll often use this style for clarity.
doSomething()
.then((result) => {
console.log(result)
})
.catch((error) => {
console.log(error)
})
Now, to be able to use this syntax, the doSomething() function implementation must
be a little bit special. It must use the Promises API.
This function receives 2 parameters. The first is a function we call to resolve the
promise, the second is a function we call to reject the promise.
Rejecting a promise means ending it with an error (which results in calling the
catch() method in who uses it).
Here’s how:
We can pass a parameter to the resolve and reject functions, of any type we want.
24. Async and Await
Async functions are a higher level abstraction over promises.
Any code that wants to use this function will use the await keyword right before the
function:
and doing so, any data returned by the promise is going to be assigned to the data
variable.
With one particular caveat: whenever we use the await keyword, we must do so
inside a function defined as async .
Like this:
The Async/await duo allows us to have a cleaner code and a simple mental model to
work with asynchronous code.
As you can see in the example above, our code looks very simple. Compare it to
code using promises, or callback functions.
And this is a very simple example, the major benefits will arise when the code is
much more complex.
As an example, here’s how you would get a JSON resource using the Fetch API, and
parse it, using promises:
getFirstUserData()
getFirstUserData()
There is a very important difference between var , let and const declarations.
A variable defined as var inside a function is only visible inside that function.
Similarly to a function arguments:
A variable defined as const or let on the other hand is only visible inside the block
where it is defined.
A block is a set of instructions grouped into a pair of curly braces, like the ones we
can find inside an if statement or a for loop. And a function, too.
It’s important to understand that a block does not define a new scope for var , but it
does for let and const .
function getData() {
if (true) {
var data = 'some data'
console.log(data)
}
}
If you call this function, you’ll get some data printed to the console.
function getData() {
if (true) {
var data = 'some data'
}
console.log(data)
}
function getData() {
if (true) {
let data = 'some data'
}
console.log(data)
}
This is because var is function scoped, and there’s a special thing happening here,
called hoisting. In short, the var declaration is moved to the top of the closest
function by JavaScript, before it runs the code. More or less this is what the function
looks like to JS, internally:
function getData() {
var data
if (true) {
data = 'some data'
}
console.log(data)
}
This is why you can also console.log(data) at the top of a function, even before it’s
declared, and you’ll get undefined as a value for that variable:
function getData() {
console.log(data)
if (true) {
var data = 'some data'
}
}
but if you switch to let , you’ll get an error ReferenceError: data is not defined ,
It can be tricky at first, but once you realize this difference, then you’ll see why var is
considered a bad practice nowadays compared to let : they do have fewer moving
parts, and their scope is limited to the block, which also makes them very good as
loop variables, because they cease to exist after a loop has ended:
function doLoop() {
for (var i = 0; i < 10; i++) {
console.log(i)
}
console.log(i)
}
doLoop()
When you exit the loop, i will be a valid variable with value 10.
If you switch to let , if you try to console.log(i) will result in an error ReferenceError:
i is not defined .
SQL
SUBSCRIBE TO MY NEWSLETTER
Enter your email
I'm not a robot
reCAPTCHA
Privacy - Terms
SUBSCRIBE NOW