This document is a cheatsheet for JavaScript you will frequently encounter in modern projects and in most contemporary sample code.
This guide is not intended to teach you JavaScript from the ground up, but to help developers with basic knowledge who may struggle to get familiar with modern codebases (or let's say to learn React for instance) because of the JavaScript concepts used.
Besides, I will sometimes provide personal tips that may be debatable, but will take care to mention that it's a personal recommendation when I do so.
Note : Most of the concepts introduced here are coming from a JavaScript language update (ES2015, often called ES6). You can find new features added by this update here; it's very well done.
When you struggle to understand a notion, I suggest you look for answers on the following resources:
- MDN (Mozilla Developer Network)
- You don't know JS (book)
- ES6 Features with examples
- WesBos blog (ES6)
- Reddit (JavaScript)
- Google to find specific blog and resources
- Modern JavaScript cheatsheet
- Introduction
- Table of contents
- Notions
- Glossary
In JavaScript, there are three keywords available to declare a variable, and each has its differences. Those are var
, let
and const
.
Variables declared with const
keyword can't be reassigned, while let
and var
can.
I recommend always declaring your variables with const
by default, and with let
if you need to mutate it or reassign it later.
Scope | Reassignable | Mutable | |
---|---|---|---|
const | Block | No | Yes |
let | Block | Yes | Yes |
var | Function | Yes | Yes |
const person = "Nick";
person = "John" // Will raise an error, person can't be reassigned
let person = "Nick";
person = "John";
console.log(person) // "John", reassignment is allowed with let
The scope of a variable roughly means "where is this variable available in the code".
- var
var
declared variables are function scoped, meaning that when a variable is created in a function, everything in that function can access that variable. Conversely, a block scoped variable created in a function can't be accessed outside this function.
Note : If a
var
variable is created outside a function, it is attached to the window object and thus globally available.
I recommand you to picture it as if an X scoped variable meant that this variable was a property of X.
function myFunction() {
var myVar = "Nick";
console.log(myVar); // "Nick" - myVar is accessible inside the function
}
console.log(myVar); // Undefined, myVar is not accessible outside the function.
More subtle example:
function myFunction() {
var myVar = "Nick";
if (true) {
var myVar = "John";
console.log(myVar); // "John"
// actually, myVar being function scoped, we just erased the previous myVar value "Nick" for "John"
}
console.log(myVar); // "John" - see how the instructions in the if block affected this value
}
console.log(myVar); // Undefined, myVar is not accessible outside the function.
- let
var
and let
are about the same, but let
declared variables are block scoped.
Taking our previous example:
function myFunction() {
let myVar = "Nick";
if (true) {
let myVar = "John";
console.log(myVar); // "John"
// actually, myVar being block scoped, we just created a new variable myVar.
// this variable is not accessible outside this block and totally independent
// from the first myVar created !
}
console.log(myVar); // "Nick", see how the instructions in the if block DID NOT affect this value
}
console.log(myVar); // Undefined, myVar is not accessible outside the function.
- const
A const
, as well as let
, declared variables are block scoped, but they can't be reassigned nor re-declared afterwards.
const myVar = "Nick";
myVar = "John" // raises an error, reassignment is not allowed
const myVar = "Nick";
const myVar = "John" // raises an error, re-declaration is not allowed
But there is a subtlety : const
variables are not immutable ! Concretely, it means that object and array const
declared variables can be mutated.
For objects:
const person = {
name: 'Nick'
};
person.name = 'John' // this will work ! person variable is not completely reassigned, but mutated
console.log(person.name) // "John"
person = "Sandra" // raises an error, because reassignment is not allowed with const declared variables
For arrays:
const person = [];
person.push('John'); // this will work ! person variable is not completely reassigned, but mutated
console.log(person[0]) // "John"
person = ["Nick"] // raises an error, because reassignment is not allowed with const declared variables
The ES6 JavaScript update has introduced arrow functions, which are another way to declare and use functions. Here are the benefits they bring:
- More concise
- this is picked up from surroundings
- implicit return
- Concision and implicit return
function double(x) { return x * 2; } // Traditional way
console.log(double(2)) // 4
const double = x => x * 2; // Same function written as an arrow function with implicit return
console.log(double(2)) // 4
- this reference
In an arrow function, this is equal to the this value of the enclosing execution context. Basically, with arrow functions you don't have to do the "that = this" trick before calling a function inside a function anymore.
function myFunc() {
this.myVar = 0;
setTimeout(() => {
this.myVar++;
console.log(this.myVar) // 1
}, 0);
}
Arrow functions are more concise than traditional functions in many ways. Let's review all the possible cases:
- Implicit VS Explicit return
An explicit return is a function where the return keyword is used in its body.
function double(x) {
return x * 2; // this function explicitly returns x * 2, *return* keyword is used
}
In the traditional way of writing functions, the return was always explicit. But with arrow functions, you can do implicit return which means that you don't need to use the keyword return to return a value.
To do an implicit return, the code must be written in a one-line sentence.
const double = (x) => {
return x * 2; // Explicit return here
}
Since there only is a return value here, we can do an implicit return.
const double = (x) => x * 2;
To do so, we only need to remove the brackets and the return keyword. That's why it's called an implicit return, the return keyword is not there but this function will indeed return x * 2
.
Note : If your function does not return a value (with side effects), it doesn't do an explicit nor an implicit return.
- Only one argument
If your function only takes one parameter, you can omit the parenthesis around it. If we take back the above double code:
const double = (x) => x * 2; // this arrow function only takes one parameter
Parenthesis around the parameter can be avoided:
const double = x => x * 2; // this arrow function only takes one parameter
- No arguments
When there is no argument provided to an arrow function, you need to provide parentheses or it won't be valid syntax.
() => { // parenthesis are provided, everything is fine
const x = 2;
return x;
}
=> { // No parenthesis, this won't work!
const x = 2;
return x;
}
To understand this subtlety introduced with arrow functions, you must understand how this behaves in JavaScript.
In an arrow function, this is equal to the this value of the enclosing execution context. What it means is that an arrow function doesn't create a new this, it grabs it from its surrounding instead.
Without arrow function, if you wanted to access a variable from this in a function inside a function, you had to use the that = this or self = this trick.
For instance, using setTimeout function inside myFunc:
function myFunc() {
this.myVar = 0;
var that = this; // that = this trick
setTimeout(
function() { // A new *this* is created in this function scope
that.myVar++;
console.log(that.myVar) // 1
console.log(this.myVar) // undefined -- see function declaration above
},
0
);
}
But with arrow function, this is taken from its surrounding:
function myFunc() {
this.myVar = 0;
setTimeout(
() => { // this taken from surrounding, meaning myFunc here
this.myVar++;
console.log(this.myVar) // 1
},
0
);
}
- Arrow functions introduction - WesBos
- JavaScript arrow function - MDN
- Arrow function and lexical this
Starting from ES2015 JavaScript update, you can set default value to your function parameters using the following syntax:
function myFunc(x = 10) {
return x;
}
console.log(myFunc()) // 10 -- no value is provided so x default value 10 is assigned to x in myFunc
console.log(myFunc(5)) // 5 -- a value is provided so x is equal to 5 in myFunc
console.log(myFunc(undefined)) // 10 -- undefined value is provided so default value is assigned to x
console.log(myFunc(null)) // null -- a value (null) is provided, see below for more details
The default parameter is applied in two and only two situations:
- No parameter provided
- undefined parameter provided
In other words, if you pass in null the default parameter won't be applied.
Note : Default value assignment can be used with destructured parameters as well (see next notion to see an example)
Destructuring is a convenient way of creating new variables by extracting some values from data stored in objects or arrays.
To name a few useful cases, destructuring can be used to destructure function parameters or this.props in React projects for instance.
- Object
Lets consider the following object for all the samples:
const person = {
firstName: "Nick",
lastName: "Anderson",
age: 35,
sex: "M"
}
Without destructuring
const first = person.firstName;
const age = person.age;
const city = person.city || "Paris";
With destructuring, all in one line:
const { firstName: first, age, city = "Paris" } = person; // That's it !
console.log(age) // 35 -- A new variable age is created and is equal to person.age
console.log(first) // "Nick" -- A new variable first is created and is equal to person.firstName
console.log(firstName) // Undefined -- person.firstName exists BUT the new variable created is named first
console.log(city) // "Paris" -- A new variable city is created and since person.city is undefined, city is equal to the default value provided "Paris".
Note : In const { age } = person;
, the brackets after const keyword are not used to declare an object nor a block but is the destructuring syntax.
- Function parameters
Destructuring is often used to destructure objects parameters in functions.
Without destructuring
function joinFirstLastName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"
In destructuring the object parameter person, we get a more concise function:
function joinFirstLastName({ firstName, lastName }) { // we create firstName and lastName variables by destructuring person parameter
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"
Destructuring is even more pleasant to use with arrow functions:
const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName;
joinFirstLastName(person); // "Nick-Anderson"
- Array
Lets consider the following array:
const myArray = ["a", "b", "c"];
Without destructuring
const x = myArray[0];
const y = myArray[1];
With destructuring
const [x, y] = myArray; // That's it !
console.log(x) // "a"
console.log(y) // "b"
Map, filter and reduce are array methods that are coming from a programming paradigm named functional programming.
To sum it up:
- Array.prototype.map() takes an array, does something on its elements and returns an array with the transformed elements.
- Array.prototype.filter() takes an array, decides element by element if it should keep it or not and returns an array with the kept elements only
- Array.prototype.reduce() takes an array and aggregates the elements into a single value (which is returned)
I recommend to use them as much as possible in following the principles of functional programming, because they are composable, concise and elegant.
With those three methods you can avoid the use of for and forEach loops in must situations. When you are tempted to do a for loop, try to do it with map, filter and reduce composed. You might struggle to do it at first because it requires you to learn a new way of thinking, but once you've got it things gets easier.
const numbers = [0, 1, 2, 3, 4, 5, 6];
const doubledNumbers = numbers.map(n => n * 2); // [0, 2, 4, 6, 8, 10, 12]
const parNumbers = numbers.filter(n => n % 2 === 0); // [0, 2, 4, 6]
const sum = numbers.reduce((prev, next) => prev + next, 0); // 21
Compute total grade sum for students above 10 by composing map, filter and reduce:
const students = [
{ name: "Nick", grade: 10 },
{ name: "John", grade: 15 },
{ name: "Julia", grade: 19 },
{ name: "Nathalie", grade: 9 },
];
const aboveTenSum = students
.map(student => student.grade) // we map the students array to an array of their grades
.filter(grade => grade >= 10) // we filter the grades array to keep those above 10
.reduce((prev, next) => prev + next, 0); // we sum all the grades above 10 one by one
console.log(aboveTenSum) // 44 -- 10 (Nick) + 15 (John) + 19 (Julia), Nathalie below 10 is ignored
Let's consider the following array of numbers for our examples:
const numbers = [0, 1, 2, 3, 4, 5, 6];
const doubledNumbers = numbers.map(function(n) {
return n * 2;
});
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]
What's happening here ? We are using .map on the numbers array, map is iterating on each element of the array and passes it to our function. The goal of the function is to produce and return a new value from the one passed so that map can replace it.
Lets extract this function to make it more clear, just for this once:
const doubleN = function(n) { return n * 2; };
const doubledNumbers = numbers.map(doubleN);
console.log(doubledNumbers); // [0, 2, 4, 6, 8, 10, 12]
numbers.map(doubleN)
produces [doubleN(0), doubleN(1), doubleN(2), doubleN(3), doubleN(4), doubleN(5), doubleN(6)]
which is equal to [0, 2, 4, 6, 8, 10, 12]
.
Note : If you do not need to return a new array and just want to do a loop that have side effects, you might just want to use a for / forEach loop instead of a map.
const parNumbers = numbers.filter(function(n) {
return n % 2 === 0; // true if "n" is par, false if "n" isn't
});
console.log(parNumbers); // [0, 2, 4, 6]
We are using .filter on the numbers array, filter is iterating on each element of the array and passes it to our function. The goal of the function is to return a boolean that will determine whether the current value will be kept or not. Filter then returns the array with only the kept values.
The reduce method goal is to reduce all elements of the array it iterates on into a single value. How it aggregates those elements is up to you.
const sum = numbers.reduce(
function(acc, n) {
return acc + n;
},
0 // accumulator variable value at first iteration step
);
console.log(sum) //21
Just like for .map and .filter methods, .reduce is applied on an array and takes a function as first parameter.
This time though, there are changes:
- .reduce takes two parameters
The first parameter is a function that will be called at each iteration step.
The second parameter is the value of the accumulator variable (acc here) at the first iteration step (read next point to understand).
- Function parameters
The function you pass as the first parameter of .reduce takes two parameters. The first one (acc here) is the accumulator variable, whereas the second parameter (n) is the current element.
The accumulator variable is equal to the return value of your function at the previous iteration step. At the first step of the iteration, acc is equal to the value you passed as .reduce second parameter.
acc = 0
because we passed in 0 as second parameter for reduce
n = 0
first element of the number array
Function returns acc + n --> 0 + 0 --> 0
acc = 0
because its the value the function returned at the previous iteration step
n = 1
second element of the number array
Function returns acc + n --> 0 + 1 --> 1
acc = 1
because its the value the function returned at the previous iteration step
n = 2
third element of the number array
Function returns acc + n --> 1 + 2 --> 3
acc = 3
because it's the value the function returned at the previous iteration step
n = 3
fourth element of the number array
Function returns acc + n --> 3 + 3 --> 6
acc = 15
because it's the value the function returned at the previous iteration step
n = 6
last element of the number array
Function returns acc + n --> 15 + 6 --> 21
As it is the last iteration step, .reduce returns 21.
The spread operator ...
has been introduced with ES2015 and is used to expand elements of an iterable (like an array) into places where multiple elements can fit.
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
function myFunc(x, y, ...params) {
console.log(x);
console.log(y);
console.log(params)
}
myFunc("a", "b", "c", "d", "e", "f")
// "a"
// "b"
// ["c", "d", "e", "f"]
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
If we have the two following arrays:
const arr1 = ["a", "b", "c"];
const arr2 = [arr1, "d", "e", "f"]; // [["a", "b", "c"], "d", "e", "f"]
arr2 first element is an array, because arr1 is injected as is into arr2. But what we want is arr2 to be an array of letters. To do so, we can spread the elements of arr1 into arr2.
With spread operator
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
In function parameters, we can use the rest operator in order to inject parameters into an array we can loop in. There is already an argument object bound to every function that is equal to an array of all the parameters passed-in to the function.
function myFunc() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
myFunc("Nick", "Anderson", 10, 12, 6);
// "Nick"
// "Anderson"
// 10
// 12
// 6
But lets say that we want this function to create a new student with its grades and with its average grade. Wouldn't it be more convenient to extract the first two parameters into two separated variables, and then have all the grades in an array we can iterate over?
That's exactly what the rest operator allows us to do!
function createStudent(firstName, lastName, ...grades) {
// firstName = "Nick"
// lastName = "Anderson"
// [10, 12, 6] -- "..." takes all other parameters passed and creates a "grades" array variable that contains them
const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // computes average grade from grades
return {
firstName: firstName,
lastName: lastName,
grades: grades,
avgGrade: avgGrade
}
}
const student = createStudent("Nick", "Anderson", 10, 12, 6);
console.log(student);
// {
// firstName: "Nick",
// lastName: "Anderson",
// grades: [10, 12, 6],
// avgGrade: 9,33
// }
Note : createStudent function is bad because we don't check if grades.length exists or is different from 0. But its easier to read this way so I didn't handled this case.
For this one I recommend you read previous explanations about the rest operator on iterables and function parameters.
const myObj = { x: 1, y: 2, a: 3, b: 4 };
const { x, y, ...z } = myObj; // object destructuring here
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// z is the rest of the object destructured : myObj object minus x and y properties destructured
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
// Here z object properties are spread into n
- TC39 - Object rest/spread
- Spread operator introduction - WesBos
- JavaScript & the spread operator
- 6 Great uses of the spread operator
When assigning a variable to an object property, if the variable name is equal to the property name, you can do the following:
const x = 10;
const myObj = { x };
console.log(myObj.x) // 10
Usually (pre-ES2015) when you declare a new object literal and want to use variables as object properties values, you would write this kind of code:
const x = 10;
const y = 20;
const myObj = {
x: x, // assigning x variable value to myObj.x
y: y // assigning y variable value to myObj.y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
As you can see, this is quite repetitive because the properties name of myObj are the same as the variable names you want to assign to those properties.
With ES2015, when the variable name is the same as the property name, you can do this shorthand:
const x = 10;
const y = 20;
const myObj = {
x,
y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
A promise is an object which can be returned synchronously from an asynchronous function (ref).
Promises can be used to avoid callback hell and they are more and more frequently encountered in modern JavaScript projects.
const fetchingPosts = new Promise((res, rej) => {
$.get("/posts")
.done(posts => res(posts))
.fail(err => rej(err));
});
fetchingPosts
.then(posts => console.log(posts))
.catch(err => console.log(err));
When you do an AJAX request the response is not synchronous because you want a resource that takes some time to come. It even may never come if the resource you have requested is unavailable for some reason (404).
To handle that kind of situations, ES2015 has given us promises. Promises can have three different states:
- Pending
- Resolved
- Rejected
Let's say we want to use promises to handle an AJAX request to fetch the resource X.
We firstly are going to create a promise. We will use the jQuery get method to do our AJAX request to X.
const xFetcherPromise = new Promise( // Create promise using "new" keyword and store it into a variable
function(resolve, reject) { // Promise constructor takes a function parameter which as resolve and reject parameters itself
$.get("X") // Launch the AJAX request
.done(function(X) { // Once the request is done...
resolve(X); // ... resolve the promise with the X value as parameter
})
.fail(function(error) { // If the request has failed...
reject(error); // ... reject the promise with the error as parameter
});
}
)
As seen in the above sample, the Promise object takes a function which takes two parameters resolve and reject. Those parameters are functions which when called are going to move the promise pending state to respectivly a resolved and rejected state.
But at the moment, the promise has not been used but only has been declared and stored into xFetcherPromise variable! So it doesn't have a current state.
To use the promise, we do the following:
xFetcherPromise
.then(function(X) {
console.log(X);
})
.catch(function(err) {
console.log(err)
})
.then
is a method that once called will put the xFetcherPromise in pending state. When called, the promise body runs and in this case an AJAX call is being done.
If it succeeds, resolve is called and the function passed as .then
parameter is executed.
If it fails, reject is called and the function passed as .catch
parameter is executed.
- JavaScript Promises for dummies - Jecelyn Yeen
- JavaScript Promise API - David Walsh
- Using promises - MDN
- What is a promise - Eric Elliott
- JavaScript Promises: an Introduction - Jake Archibald
- Promise documentation - MDN
Template literals is an expression interpolation for single and multiple-line strings.
In other words, it is a new string syntax in which you can conveniently use any JavaScript expressions (variables for instance).
const name = "Nick";
`Hello ${name}, the following expression is equal to four : ${2+2}`;
// Hello Nick, the following expression is equal to four: 4
ES6 modules are used to access variables or functions in a module explicitly exported by the modules it imports.
I highly recommend to take a look at MDN resources on import / export (see external resources below), it is both simple and complete.
- Named exports
Named exports are useful to export several values from a module. You can only name-export variables (not functions or class), so if you want to name-export a function, you have to store it in a variable before.
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // Destructuring import
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // Inject all exported values into constants variable
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7
- Default import / export
Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is considered the "main" exported value since it will be the simplest to import. Ref: MDN
// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;
// ------------
// myFile.js
import number from './coolNumber.js';
// Default export, independently from its name, is automatically injected into number variable;
console.log(number) // 42
Function exporting:
// sum.js
export default function sum(x, y) {
return x + y;
}
// -------------
// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3
this operator behaves differently than in other languages and is in most cases determined by how a function is called. (Ref: MDN).
This notion having many subtleties and being quite hard, I highly suggest you to deep dive in the external resources below. Thus, I will provide what I personally have in mind to determine what this is equal to. I have learned this tip from this article written by Yehuda Katz.
function myFunc() {
...
}
// After each statement you find the value of *this* in myFunc
myFunc.call("myString", "hello") // "myString" -- first .call parameter value is injected into *this*
// In non-strict-mode
myFunc("hello") // window -- myFunc() is syntax sugar for myFunc.call(window, "hello")
// In strict-mode
myFunc("hello") // undefined -- myFunc() is syntax sugar for myFunc.call(undefined, "hello")
var person = {
myFunc: function() { ... }
}
person.myFunc.call(person, "test") // person Object -- first call parameter is injected into *this*
person.myFunc("test") // person Object -- person.myFunc() is syntax sugar for person.myFunc.call(person, "test")
var myBoundFunc = person.myFunc.bind("hello") // Creates a new function in which we inject "hello" in *this* value
person.myFunc("test") // person Object -- The bind method has no effect on the original method
myBoundFunc("test") // "hello" -- myBoundFunc is person.myFunc with "hello" bound to *this*
JavaScript is a prototype oriented language (whereas Java is object oriented for instance). ES6 has introduced JavaScript classes which are meant to be a syntactic sugar for prototype-based inheritance and not a new object-oriented inheritance model (ref).
The word class is indeed error prone if you are familiar with classes in other languages. If you do, avoid assuming how JavaScript classes work on this basis and consider it an entirely different notion.
Since this document is not an attempt to teach you the language from the ground up, I will consider you know what prototypes are and how they behave. But here are some links I found great to understand this notion:
- Understanding Prototypes in JS - Yehuda Katz
- A plain english guide to JS prototypes - Sebastian Porto
- Inheritance and the prototype chain - MDN
Before ES6, prototype syntax:
var Person = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.stringSentence = function() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}
With ES6 class syntax:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
stringSentence() {
return "Hello, my name is " + this.name + " and I'm " + this.age;
}
}
const myPerson = new Person("Manu", 23);
console.log(myPerson.age) // 23
console.log(myPerson.stringSentence()) // "Hello, my name is Manu and I'm 23
For prototype understanding:
- Understanding Prototypes in JS - Yehuda Katz
- A plain english guide to JS prototypes - Sebastian Porto
- Inheritance and the prototype chain - MDN
For classes understanding:
The context in which values and expressions are "visible," or can be referenced. If a variable or other expression is not "in the current scope," then it is unavailable for use.
Source: MDN
A variable is said to have been mutated when its initial value has changed afterwards.
var myArray = [];
myArray.push("firstEl") // myArray is being mutated
A variable is said to be immutable if it can't be mutated.
Check MDN Mutable article for more details.