alias |
---|
JS |
[!question]- Interview Emphasis Points
Concepts / sections to focus on when reading
- Event Delegation / Bubbling
- DOM traversal & manipulation
- RegEx
this
- [[Hoisting]]
- IIFEs
- Prototypes: Prototype Chain & Inheritance
- Scope & Closure
- Asynchronous Programming
- Async/Await
- Callbacks
- Promises
setTimeout
&setInterval
- Data Structures: Maps & Sets
- Functional Programming
- HOFs
- [[Immutable]]
- Pure Functions
- First-class Functions
- Recursion
- Currying
- Design Patterns: Observer Pattern & Module Pattern
- JavaScript is
- Lightweight
- [[Interpreted Language|Interpreted]] & [[Dynamically-Typed Language]]
- Most modern JavaScript interpreters use [[Just-In-Time Compilation]] to improve performance.
- It runs on any device that has a special program called the JavaScript engine:
- SpiderMonkey - Firefox
- V8 - Chromium
- Javascript Core - Safari
- The JavaScript runtime is single threaded; it can only run one function at a time.
- Everything in JS is an object and can be stored in a variable.
- A single
<script>
tag can't have asrc
attribute and content inside. - The
type
andlanguage
attributes are no longer required.
-
[[Variables]]
- are containers for storing values.
- Due to design flaws with
var
, it's recommended to use modern versionslet
.var
causes confusion because it allows [[hoisting]] and redeclaration of variables.- Unlike
let
,var
has no block scope; it creates either function-scoped or global-scoped variables.
- Unlike
- names can start with an underscore (
_
) or a dollar sign ($
), in addition to letters.
-
Constants
- are like variables except that:
- they must be initialized upon declaration.
- after initializing, a new value can't be assigned to them.
- are like variables except that:
let a; // ✅ valid, no error
const b; // ⛔ will throw an error
let c;
c = 1; // ✅ valid, no error
const d;
d = 1; // ⛔ will throw an error
- For reference types like objects, the content of the value that a constant names can be changed.
const person = { name: "John Doe" };
person.name = "Jane Doe"; // ✅ valid
- Comments
- Single line:
// comment
- Multi-line:
/* comment */
- Nested comments are not supported using the multi-line syntax.
- Single line:
- Script execution blocks page rendering.
async
anddefer
allow scripts to be downloaded in a separate thread without interfering with the page loading process.async
execute as soon as the download is complete. They should be used to load independent scripts and background scripts that don't interfere with rendering.- e.g. loading data that could be used later on.
defer
is similar toasync
but script is executed after document is done being parsed.- Scripts will run in the order they appear in the page; they get executed as soon as the script and content have finished downloading.
- It's important to use the appropriate attributes and context to load scripts.
- Source: MDN
Arithmetic
+
,-
,*
,/
,%
,**
(exponent)a**x
is equivalent toMath.pow(a, x)
.
Increment / Decrement
++
&--
- These can't be applied directly to a number, but the variable holding the number.
Comparison
==
,!=
,===
,!==
,<
,>
,<=
,>=
Logical
&&
(and),||
(or),!
(not / negation)&&
- finds the first '==falsy==' value; has higher precedence than||
.||
- finds the first 'truthy' value.
Bitwise
&
(AND),|
(OR),~
(NOT),^
(XOR), ...
Assignment
- Chained assignments are evaluated from right to left.
let a, b, c;
a = b = c = 2 + 2;
// evaluates to
c = 2 + 2;
b = c;
a = c;
- Shortcut operators like
+=
and%=
are called ==augmented assignment operators==.
- Done automatically in JS.
- Reachable objects are retained in memory.
- [[Immutable]] data types.
- With the exception of
null
andundefined
, primitives are treated like objects; they have object equivalent wrappers, and hence inherited methods. - Object wrappers that provide certain functionality are created on demand and then destroyed.
[!example] When the
toUpperCase()
function is called on a string, a special object wrapper with the string value is created. After the method runs and returns, the wrapper is destroyed.Due to the lack of such wrapper objects,
null
&undefined
are considered the most primitive.
- To keep primitives as lightweight as possible, constructors (
String
/Number
/Boolean
) should only be reserved for internal use only; using those functions without thenew
keyword is fine.
- Integers are floating-point numbers without a fraction; either negative or positive.
- Floats have decimal points and decimal places, for example 2.5, and 9.77.
- Doubles are a type of float with greater precision than standard floats.
Note
JavaScript primarily has one data type for dealing with integers and decimals - Number
. But, it also has a second number type, BigInt
which is used for really large integers.
- The range for the "normal" number types is between
$-(2^{53} - 1)$ and$2^{53} - 1$ . -
BigInt
values can be created by appendingn
to the end of an integer.
const bigInteger = 012345678901234567890123456789n;
const sameBigInteger = BigInt("012345678901234567890123456789");
To call a method directly on a number, the number must either be wrapped in parenthesis or be followed by two dots:
..
.
- Special numeric values also exist:
Infinity
,-Infinity
,NaN
(which represents a computational error).- The value of
NaN
is unique; it is not equal to anything, not even to itself. - Stored in 64-bit format; if a number overflows this storage, it becomes
Infinity
.
- The value of
let num = 15 / 0; // Infinity
let num = -15 / 0; // -Infinity
-
Numbers can start with certain characters that represent the numeral system they belong to:
- Binary ->
0b
; e.g.0b11001
- Hexadecimal ->
0x
; e.g.0x19
- Octal ->
0o
; e.g.0o31
- Binary ->
-
num.toString(base)
can be used to "convert" a number to the given base numeral system and return a string representation;2
<=base
<=36
, default value is10
.
// Number conversion
(25).toString(2); // -> "11001"
(25).toString(8); // -> "31"
(25).toString(16); // -> "19"
(25).toString(36); // -> "p"
- To increase readability, numbers can be separated with
_
; trailing zeros can be shortened using exponentiation.
// Ways to write numbers
let million = 1_000_000;
let billion = 1e9;
let micro = 1e-6;
- Converting String to Number:
Number("25")
- Converting Number to String:
(25).toString()
-
Original Converted undefined
NaN
null
0
string
whitespaces trimmed; empty -> 0
; error ->NaN
- Numbers inherit methods from the
Number.prototype
object.- e.g.
parseInt()
,parseFloat()
,toFixed()
,isNaN()
,isFinite()
andtoString()
- e.g.
parseInt()
&parseFloat()
read a number left to right from a string until they can't:
parseInt("50px"); // 50
parseFloat("2.5em"); // 2.5
parseInt("2.5"); // 2
parseFloat("2.5.5"); // 2.5
- Numbers are stored in binary form; simple decimal fractions translate to unending fractions in their binary form. This causes loss of precision.
- There is no possible way to store an exact fraction like
0.2
. - A single number might not be obvious because they are usually rounded to the nearest number; it becomes evident that precision loss exists when an operation is performed.
- The most reliable method to resolve this issue is to use
toFixed()
to round the result:
(0.1).toFixed(20); // -> 0.10000000000000000555
let s = 0.7 + 0.2; // -> 0.8999999999999999
+s.toFixed(2); // -> 0.9
Other weird cases:
- Two zeros exist:
0
and-0
9999999999999999; // -> 10000000000000000
-0 === 0; // -> true
Object.is(-0, 0); // -> false
NaN === NaN; // -> false
Object.is(NaN, NaN); // -> true
- Expressions can be included in template literals.
- Template literals respect line breaks in strings. When writing normal strings, this can be achieved using
"\n"
.
const str1 = `Hello, World!
This is a string.`;
// 👆🏾 is equivalent to 👇🏾
const str2 = "Hello, World!\nThis is a string.";
- Special characters like
\n
count towards the length of a string:"Hi\n".length
->3
- Strings inherit methods from the
String.prototype
object. e.g.substring()
,indexOf()
,concat()
andtoString()
.- Using these methods creates new strings; it doesn't modify existing ones. ([[Immutable]])
- When comparing values of different types, JavaScript converts the values to numbers.
"2" > 1; // true, "2" becomes 2
- The correct way to compare strings:
localeCompare()
alert(2 + 2 + "1"); // "41" and not "221"
alert("1" + 2 + 2); // "122" and not "14"
true
/false
- Falsy values evaluate to false when executed in a boolean operation.
false
,null
,undefined
,""
(empty string),0
,NaN
- Truthy values evaluate to true when executed in a boolean operation.
- All non-falsy values and objects are truthy.
- A non-empty string always evaluates to true. e.g.
"0"
- Represents an unassigned value.
- Doesn't have a wrapper object.
typeof null
->"object"
- Represents the intentional absence of any object value.
- Doesn't have a wrapper object.
- Used to create unique identifiers for objects.
- Can be used to create hidden object properties.
- Ignored by
Object.keys()
&for...in
loops.
let id = Symbol("id"); // symbol with optional description "id"
let obj = {
[id]: 1
name: "Jane"
}
Important
- Coercion is the automatic or implicit conversion of a type.
- e.g. Adding a number to a string will result in the number being coerced into a string and concatenated.
- Unlike coercion, Type Casting is an explicit and deliberate operation.
- Typically done using functions like
Number()
,String()
orBoolean()
.
- Typically done using functions like
-
Mutable, but can be made [[Immutable]] with
Object.freeze(obj)
. -
Unlike primitives, objects are stored and copied by reference; a variable assigned to an object contains its address in memory.
-
Two objects are equal only if they reference the same object:
let a = {};
let b = a;
let c = {};
a == b; // true
a === b; // true
a == c; // false
-
Multi-word property names must be quoted.
-
There are no restrictions on object property keys, even reserved words (like
for
andlet
) are allowed. -
Only strings and symbols can be used as object keys; other types are converted to strings. e.g.
0
->"0"
-
Reading a non-existing property just returns
undefined
.
// object literal
const person = {
name: "John",
greet: function () {
console.log(`Hi, my name is ${this.name}`);
}
// OR shortly,
// greet() {
// console.log(`Hi, my name is ${this.name}`);
// }
};
console.log(user.newProp === undefined); // true
console.log("newProp" in person); // false
-
Using
in
results in more accurate property existence checks.undefined
equality fails when a property exists but has an explicitundefined
value:obj.key = undefined
-
Object keys that are integers are sorted; other types follow their creation order.
-
==Optional chaining== (
?.
) can be used to solve the "non-existing property" problem. It does that by stopping the evaluation if the value before?.
isundefined
ornull
and returnsundefined
.
let p = {};
p.name.first; // -> TypeError; p.name is undefined
p?.name; // -> undefined
p?.name?.first; // -> undefined
- When assigning object property values using variables, if the key-value names are identical, we can use the shorthand method.
function newPerson(name, age) {
return {
name, // name: name
age // age: age
};
}
typeof []
->"object"
Array.isArray([])
->true
- Store ordered collections.
- Trailing commas are allowed.
- The
length
property is writable; modifying it is an irreversible process. Decreasing it truncates the array. It can also be set to0
to clear an array. - Arrays shouldn't be compared using
==
. - The
delete
operator on an array doesn't shift items after deletion; length remains the same.
Note
During an operation, if the index of an array is not available, it will return undefined
; there are no "index out of range" exceptions.
pop()
andunshift()
methods can add multiple values at once.- Methods that work with the end of an array (
pop()
&push()
) are faster than ones that work with the beginning (shift()
&unshift()
). Array.prototype.toString()
has the same result asArray.prototype.join()
.
[] + 1; // -> "1"
[1] + 2; // -> "12"
[1, 2] + 3; // -> "1,23"
- Add / Remove Items
arr1.concat(arr2)
arr.fill(value, start, end)
arr.slice()
arr.splice()
- insert, remove and replace elements in an array.
- Iterate
arr.entries()
arr.forEach(elt, index, array)
arr.keys()
arr.values()
- Search / Lookup
arr.at(index)
arr.filter()
arr.find()
,arr.findIndex()
,arr.findLast()
,arr.findLastIndex()
,arr.includes(value)
arr.indexOf(value)
,arr.lastIndexOf(value)
- Transform
arr.map()
arr.reduce(reducerFn, initValue = arr.at(0))
arr.reduceRight(reducerFn, initValue = arr.at(-1))
arr.reverse()
arr.sort()
/arr.reverse()
arr.splice(start[, deleteCount, ...newItems])
/arr.toSpliced()
arr.split()
/arr.join()
arr.toSorted()
/arr.toReversed()
Note
A reducer function (in reduce()
and reduceRight()
methods) is called on each element with the return value of the calculation from the previous element. Final output is a single value.
const strArr = ["H", "e", "l", "l", "o", "!"];
const forwardStr = strArr.reduce((accumulator, el) => accumulator += el, "")
const reverseStr = strArr.reduceRight((accumulator, el) => accumulator += el, "")
// forwardStr: "Hello!"
// reverseStr: "!olleH"
- Static Methods
Array.from(arrayLike, mapFn)
Array.isArray()
Array.of()
- Sparse arrays are arrays that contain 'empty slots'.
- They can be created in several ways:
const x = new Array(5)
const y = [1, 2, , , 5]
const z = [1, 2]
z[4] = 5
z.length = 10
const w = [1, 2, 3, 4, 5]
delete w[2]
- Iterable objects implement the
Symbol.iterator
method. It allows us to make any object loopable or "iterable" in afor...of
loop. - Array-likes have indices and a
length
. Array.from()
creates a real array from an array-like or an iterable value.- Read more 📄
- A collection of key-value pairs (like an object), but insertion order is remembered, and either key or value can be of any type: object and primitive.
- Setting and getting values is and should be done through
set()
andget()
methods.set()
is chainable.
- It uses a similar approach to strict equality to compare keys, but
NaN
is considered equal toNaN
. - can be looped using
for...of
andforEach(value, key, map)
. - Other methods include
has(key)
,delete(key)
, andclear()
.- The
keys()
,values()
, andentries()
methods can be used for iterating;entries()
is the default used in afor...of
loop.
- The
- Maps also have a
size
attribute that returns the number of pairs.
let mapOne = new Map();
mapOne.set(1, "one").set("2", "two").set(true, "three");
let mapTwo = new Map([
[1, "one"],
["2", "two"],
[true, "three"]
]);
let mapThree = new Map(
Object.entries({
name: "Jane",
age: 35
})
);
- Maps whose keys can only be objects, not primitives.
- Unlike Maps, it doesn't prevent keys from being garbage-collected; they are "weakly-held".
- No support for iterations.
- Read more 📄
- A collection of values (like an array), where duplicates are not allowed.
- Share similar functionality and methods with Maps.
- can be looped using
for...of
andforEach
. - Set methods include
has(value)
,add(value)
,delete(value)
,clear()
.- The
keys()
,values()
, andentries()
methods can be used for iterating;entries()
is the default used in afor...of
loop.
- The
- Sets have a
size
attribute that returns the number of elements.
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits, some users come multiple times
set.add(a);
set.add(b);
set.add(c);
set.add(b);
set.add(a);
- Behave similar to WeakMaps: only object values allowed.
- No support for iterations.
- Read more 📄
- Object properties are of two kinds: data properties and accessor properties.
- Accessor properties are functions that look like regular properties.
- They are represented by getter and setter methods that execute on getting and setting a value.
- They are not called like a method but read as a property.
let person = {
firstName: "Jane",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
}
};
console.log(person.fullName); // Jane Doe
person.fullName = "John Smith";
console.log(person.firstName, person.lastName); // John, Smith
- Accessor properties don't have
value
andwritable
descriptors. They instead haveset
andget
functions that are called when the property is set and when it's read respectively.
-
Besides
value
, object properties have 3 special attributes / flags:writable
- determines whether or not a value is read-only or changeableenumerable
- determines whether or not a value can be listed in loopsconfigurable
- determines whether or not the property can be deleted and its flags can be modified.
-
By default, all flags are
true
. -
Object.getOwnPropertyDescriptor
can be used to get full info about a property. -
Object.defineProperty
can be used to change the property flags and its deletion; it allowsvalue
to be changed.
Changing a property to be non-configurable can't be undone. It can't be reverted using
defineProperty
.
- Multi-flag versions of the above methods (
Object.getOwnPropertyDescriptors
andObject.defineProperties
) exist, and they can be used to clone objects along with all their property descriptors, symbolic and non-enumerable properties.- This can't be done using
for...in
loops.
- This can't be done using
let cloneObj = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(obj)
);
- Methods exists that allow us to do whole object configuration instead of individual property configuration:
Object.preventExtensions(obj)
,Object.seal(obj)
,Object.freeze(obj)
.
Object.assign()
- shallow; copies both string and symbol properties. Nested objects are copied by reference.structuredClone()
- deep clone, with the exception of methods.
- Property names are evaluated or "computed" from a variable or an expression.
let veg = prompt("Which veggie to buy?", "peppers");
let cart = {
[veg]: 5,
[`Bell ${veg}`]: 4
};
console.log(cart.peppers); // 5
console.log(cart["Bell peppers"]); // 4
- Works on any iterable.
let person = {};
[person.firstName, person.lastName] = "Jane Doe".split(" ");
let [a, , c, ...remaining] = "abcde";
a; // -> "a"
c; // -> "c"
remaining; // -> ["d", "e"]
- Can be used to swap values.
[a, c] = [c, a];
a; // -> "c"
c; // -> "a"
- Absent values are
undefined
; Default values, expressions or function calls can replace missing values.
let [x, y] = [];
x; // -> undefined
y; // -> undefined
let [x = 0, y = 0] = [10];
x; // -> 10
y; // -> 0
- Similar syntax applies for object destructuring:
let { firstName, lastName } = {
firstName: "Jane",
lastName: "Doe"
};
- The global object provides variables and functions that are built into the language or environment and are available anywhere.
- Browsers:
window
- Node.js:
global
- Recent cross-environment standardized name for the global object:
globalThis
- Browsers:
- Functions and variables declared in the global-scope with
var
(notlet
orconst
) become properties of the global object. - Support for modern browser features can be checked by checking their availability as a global object property. Necessary polyfills can the be added.
if (!window.Promise) {
// Promise polyfill
}
-
Constructor functions are a way to define the an object's template; it contains the set of methods and the properties it can have.
-
By convention, they start with a capital letter and name the object type they create; they don't have a return statement.
function EV(make) {
this.make = make;
this.describe = function () {
console.log(`The ${this.make} is an electric vehicle brand.`);
};
}
const rivian = new EV("Rivian");
console.log(rivian.make);
rivian.describe();
- Immediately called constructor functions can be used to create a single complex object:
const rivian = new (function () {
this.make = "Rivian";
// ...
})();
- Every object in JavaScript has a built-in property - its prototype.
- Every function has a prototype that references an object, which contains properties and methods shared by all instances created using that function as a constructor.
- And because the prototype is itself an object, it will have its own prototype. This is called a prototype chain.
[!note] Prototypes allow for inheritance in JavaScript.
- When accessing a property on an object, JavaScript looks for it on the object itself. If not found, it looks up the prototype chain.
__proto__
is a getter / setter for an object's[[Prototype]]
; it exist for historical reasons. Modern JS recommends the use ofObject.getPrototypeOf
/Object.setPrototypeOf
functions instead.- For a constructor function
F()
, if theF.prototype
is set to be an object, creating an object usingnew F()
sets its[[Prototype]]
to that value. This is done only at the time of object creation; changing the value ofF.prototype
after object creation doesn't change the prototype of already created objects.
let car = {
numWheels: 4
};
function ElectricCar(name) {
this.name = name;
}
ElectricCar.prototype = car; // overwrites the default prototype
let ev = new ElectricCar("Rivian"); // ev.__proto__ == car
console.log(ev.numWheels); // 4
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log("hello!");
};
const john = new Person("John");
john.greet(); // hello!
Object.create()
can be used to create an object with a specified prototype object.
const personPrototype = {
greet() {
console.log("hello!");
}
};
const john = Object.create(personPrototype);
john.greet(); // hello!
- Function Constructors
const personPrototype = {
greet() {
console.log(`Hi, my name is ${this.name}`);
}
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// same as
// Person.prototype.greet = personPrototype.greet;
Storage.prototype.set = function (key, value) {
this.setItem(key, JSON.stringify(value));
};
Storage.prototype.get = function (key) {
var value = this.getItem(key);
return value && JSON.parse(value);
};
localStorage.set("obj", {
name: "john",
age: 34
});
console.log(localStorage.get("obj"));
Only object properties and methods are shared; but an object's state is not.
Object.prototype
is the most basic prototype; all objects have it by default. Its prototype isnull
.- All properties of
Object.prototype
have an [[enumerable]] value offalse
.
- All properties of
- The prototype can only either be an object or
null
. this
isn't affected by prototypes; in a method, a getter or a setter call,this
refers to the object before the dot.
- Properties that are defined directly in the object, and not on the prototype, are called own properties.
Object.keys()
andObject.values()
only return own properties.for...in
loops iterate over both own and inherited properties.
// Using the above code
const john = new Person("John");
console.log(Object.hasOwn(john, "name")); // true
console.log(Object.hasOwn(john, "greet")); // false
Note
Polymorphism is when a method has the same name but a different implementation in different classes.
Note
Delegation is a programming pattern where an object, when asked to perform a task, can perform the task itself or ask another object (its delegate) to perform the task on its behalf.
[!important] Classes in JS are syntactic sugar over the existing prototype-based inheritance.
- Class fields and methods are public by default.
- By convention, protected fields are prefixed with an underscore (
_
). They can be inherited and accessed from a subclass. - In modern JS, prepending a property or a method with
#
makes it private.- It can only be accessed internally.
- It can't be accessed using bracket notation.
- By convention, protected fields are prefixed with an underscore (
- Just like literal objects, classes may include getters/setters, computed properties etc.
- Omitting a setter method makes the property read-only.
class User {
#name;
constructor(name) {
this.#name = name;
}
get name() {
return this.#name;
}
set name(value) {
if (typeof value !== "string") {
alert("Invalid Data Type");
return;
}
this.#name = value;
}
}
// Class Expression
let User = class {
sayHi() {
alert(MyClass); // MyClass name is visible only inside the class
}
};
// Named Class Expression
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass name is visible only inside the class
}
};
- [[Static Properties & Methods]] can be created in a JS class using the
static
keyword.- With the exception of built-in classes, they can be inherited.
class User {
static staticMethod() {
alert(this === User);
}
}
/* ====== OR ====== */
class User { }
User.staticMethod = function() {
alert(this === User);
};
User.staticMethod(); // true
- Taking inheritance into account, the
instanceof
operator allows to check whether an object belongs to a certain class.
john instanceof User
- If a subclass has its own initializations, it must first call the superclass constructor using
super()
, and pass any parameters that the superclass constructor expects. - When a subclass method replaces the superclass's implementation, it overrides the version in the superclass.
class Person {
name; // optional; can be initialized to a default value
// can be omitted
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi, my name is ${this.name}.`);
}
}
class Professor extends Person {
#teaches;
constructor(name, teaches) {
super(name);
this.#teaches = teaches;
}
greet() {
super.greet();
this.#introduce();
}
#introduce() {
console.log(`I will teach you ${this.#teaches}.`);
}
}
const john = new Professor("John", "Physics");
john.greet(); // Hi, my name is John, and I will teach you Physics.
john.#teaches; // SyntaxError
Important
Class fields are set on individual objects, not on the Class.prototype
.
- To access its containing object, a method can use the
this
keyword; its value is the object used to call the method. e.g. Inuser.greet()
,this
isuser
. this
is contextual; Its value is evaluated during the run-time, depending on the context.
function fn() {
console.log(this);
}
let user = {};
user.f = fn;
user.f(); // -> user
fn(); // -> window (non-strict mode)
fn(); // -> undefined (strict mode)
- Arrow functions don't have
this
; they inherit thethis
of the nearest non-arrow function ancestor.
Execution Context | Code | Value of this |
---|---|---|
Global | N/A | global object (e.g. window ) |
Function (Method call) | myObj.foo(); |
myObj |
Function (Baseless function call) | foo(); |
global object (e.g. window ) (undefined in strict mode) |
Function (Using call ) |
foo.call(context, myArg); |
context |
Function (Using apply ) |
foo.apply(context, [myArgs]); |
context |
Function (Constructor with new) | const newFoo = new Foo(); |
the new instance (e.g. newFoo ) |
![[Functional Programming|FP]]
The scope is the current context of execution in which values and expressions are available or can be referenced.
- If scopes are layered in hierarchy, child scopes have access to parent scopes, but not vice versa.
- Unlike
var
, variables declared withlet
orconst
belong to an additional scope they were created in. They are block-scoped. - Variables declared inside a code block (
{...}
,if
,for
,while
) are only visible inside that block.
- Functions that remember and have access to their outer scope / environment.
- Use cases:
- Create private variables and methods for encapsulating data.
- A commonly used pattern in module design to hide implementation details.
- Implement memoization, caching expensive function results for improved performance.
- Used in recursive algorithms.
- Create function factories (functions with customized behavior).
- Used in [[functional programming]] and for creating specialized function.
- Create private variables and methods for encapsulating data.
- All [[JavaScript]] functions are inherently closures (with the exception of the
new Function
-created ones). - A function has memory of the environment it was called in.
- Read more 📄
// Encapsulation
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // Outputs: 2
// Memoization
function memoizedFib() {
const cache = {
"0": 1,
"1": 1,
}
return function fib(n) {
if (n < 2 || n in cache) {
return cache[n];
}
cache[n] = fib(n-1) + fib(n-2);
return cache[n];
}
}
// Function Factories
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // Outputs: 10
console.log(triple(5)); // Outputs: 15
switch (expression) {
case case1:
// code
break;
case case2:
// code
break;
case case3:
case case4:
// code for grouped case
break;
/*...*/
default:
// code
}
Important
Equality check with switch
statements is strict.
for ... in
iterates over all the [[enumerable]] properties (keys or indices) of an object.for ... of
iterates over the numeric property values of an object.
const arr = ["x", "y", "z"];
for (let i in arr) {
console.log(i); // '0', '1', '2'
}
for (let i of arr) {
console.log(i); // 'x', 'y', 'z'
}
map()
/filter()
- create new collections with operations performed.- Standard
for
loop:
for (initializer; condition; finalExpression) {
// code to run
}
while
loop
initializer;
while (condition) {
// code to run
finalExpression;
}
do...while
loop - code is always executed at least once.
initializer;
do {
// code to run
finalExpression;
} while (condition);
break
- exit loops or a block of code entirely.continue
- skip to the next iteration; only used with loops.label
- prefix a statement with an identifier to refer to it later withbreak
orcontinue
.
let i, j;
loop1: for (i = 0; i < 3; i++) {
// first 'for' statement - "loop1"
loop2: for (j = 0; j < 3; j++) {
// second 'for' statement - "loop2"
if (i === 1 && j === 1) {
break loop1;
}
console.log(`i = ${i}, j = ${j}`);
}
}
condition ? 'if' code : 'else' code
- Directives like
break
andcontinue
can't be used with this operator. - It's recommended to use this operator to return a value depending on a condition.
- Avoid using it as a replacement for
if...else
statements to run expressions.
- Avoid using it as a replacement for
// ⛔
age >= 18 ? alert("yes") : alert("no");
// ✅
let accessAllowed = age >= 18 ? "yes" : "no";
- Syntax errors are spelling errors that cause the program to stop running part way thru.
- Logic errors are errors resulting in incorrect or unintended results.
- Errors are commonly handled using
try...catch
statements. - Errors with asynchronous code can be handled using the
.catch()
method with Promises as well as using atry...catch
block withasync
/await
.
openMyFile();
try {
await writeMyFile(theData); // This may throw an error
} catch (e) {
handleError(e); // If an error occurred, handle it
} finally {
closeMyFile(); // Always close the resource
}
- Errors can be thrown using
throw
.
throw new Error("This is a custom error");
- Custom error types can also be created by extending the
Error
class.
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
}
}
throw new CustomError("This is a custom error");
- Unlike [[Java]], JavaScript doesn't support multiple catch blocks.
- A common workaround is using conditionals to check the types.
try { /* ... */ }
catch (error) {
if (error instanceof TypeError) {
console.error("Type error:", error.message);
} else if (error instanceof RangeError) {
console.error("Range error:", error.message);
} else {
console.error("Unknown error:", error.message);
}
}
Error
- Generic errorSyntaxError
- Syntax error in the codeReferenceError
- Reference to anundefined
variableTypeError
- Operation on an inappropriate typeRangeError
- Number outside of valid range
Functions are of object type.
- [[Method]]s - functions that are part of objects.
- Objects arguments are passed by reference.
- Functions created thru a declaration are [[Hoisting|hoisted]]; They can be invoked before they're defined.
- In strict mode, their scoping is limited to the block they are in.
- Functions create thru an expression are assigned to a variable at run time when their execution is reached; hence, they can't be invoked before their definition.
- Anonymous functions don't have a name; they are often passed as arguments to other functions. e.g.
input.addEventListener("keypress", function(e) {})
. - Functions can also be created from a string that's passed at run time using the
new Function()
syntax.- Can be used to execute code received from a server dynamically.
- This type of function doesn't remember the environment it's created in; The
[[Environment]]
is set to reference global.
- Named function expressions (NFEs) allow a function to reference / access itself only internally.
// Function declaration / statement
function fn() {
/* code */
}
// Function expression / literal
const fn = function () {
/* code */
};
// Named function expression (NFE)
const fn = function func() {
/* code */
};
fn(); // ✅
func(); // ⛔
// Arrow functions
const fn = () => {
/* code */
};
// 'new Function' syntax
let fn = new Function([arg1, arg2, ...argN], functionBody);
let add = new Function("a", "b", "return a + b");
Passing in named parameter functions with parenthesis calls the function immediately.
input.addEventListener("keypress", fn()); // ⛔
input.addEventListener("keypress", fn); // ✅
- Parameters can have default values:
function fn(a, b = 5) {
/* code */
}
function fn(a, b = getSum()) {
/* code */
}
-
When a function is passed as default parameter,
- it is evaluated when the calling function is ran.
- it is evaluated every time if and only if the second parameter is not provided.
-
An empty
return
statement is the same asreturn undefined;
. -
No
return
statement in a function returnsundefined
. -
Functions passed to other functions as arguments are called callback functions; they used to be the main way async functions were implemented.
- Consider this piece of code:
function greet(phrase) {
console.log(`${phrase}, ${this.name}!`);
}
let user = {
name: "John",
introduce() {
console.log(`My name is ${this.name}.`);
}
};
fn.call(context, ...args)
- Provides a
this
context a function can execute in. - It takes an expanded list of arguments.
- It calls the function with the context it provides.
greet.call(user, "Hello"); // Hello, John
fn.apply(context, args)
- Similar to
call
, but it takes an array-like object argument containing the function arguments.
greet.apply(user, ["Hello"]); // Hello, John
const nums = [5, 1, 4, 3, 9];
const max = Math.max.apply(null, nums);
let boundFn = fn.bind(context, ...args)
- Creates a new function when invoked takes into account the provided context and the sequence of arguments.
- When an object method is passed as callback (for instance to
setTimeout
), it loses its context (this
).
setTimeout(user.introduce, 1000); // My name is undefined.
- A wrapper function can solve this problem:
setTimeout(() => user.introduce(), 1000); // My name is John.
But if the value of
user
changes before the callback is executed, this will call the wrong object.
- Using
bind
helps with this issue:
let greetUser = greet.bind(user, "Hello");
greetUser("Hello"); // Hello, John
let introduce = user.introduce.bind(user);
setTimeout(introduce, 1000);
-
don't have their own
this
,arguments
,super
ornew.target
-
always anonymous
-
can't be invoked with
new
-
shouldn't be used as methods or constructors
-
not suitable for
call
,apply
, andbind
due to scoping. -
Trying to access
this
will refer to thethis
of the closest non-arrow ancestor function.
let users = {
name: "Users",
list: ["John", "Jane", "Alice"]
};
users.displayList = function () {
this.list.forEach((user) => {
// this -> users
console.log(`${this.name}: ${user}`);
});
};
users.displayList = function () {
this.list.forEach(function (user) {
// TypeError: this is undefined
console.log(`${this.name}: ${user}`);
});
};
- The execution context stores information about the execution process of a running function. It is an internal data structure which contains details of a function execution: current position of the control flow, current variables, the value of
this
, etc; one per each function call. - When performing nested calls, the current execution context is stored in a stack for retrieval later; this is called the execution context stack.
- The maximum number of nested calls (or subcalls) in a recursive function is called the recursion depth; this number is limited in [[JavaScript]] by the engine.
- The recursion depth is also equal to the maximal number of context in the execution context stack.
- JS doesn't throw an error due to excessive arguments in a function call.
- Remaining or excessive parameters can be passed using the spread operator (
...
); it must be at the end of the parameter list and can be accessed inside the function using array syntax.
function sum(a, b, ...nums) {}
- In non-arrow functions, [[JavaScript]] also provides a special array-like iterable object,
arguments
, which contains indexed list of all arguments.
[!info] The Spread Operator (
...
) It expands an iterable object like a, string, an array or an object into a list of their values.It can be used to clone arrays and objects.
-
When an event is fired on an element with parents, the browser runs three different phases:
- Capturing
- If the element's outermost ancestor,
<html>
, has an event (e.g.click
) handler registered on it, it's ran. - It moves 'down' to the next element inside
<html>
and performs the same task until it reaches the direct parent of the element.
- If the element's outermost ancestor,
- Target
- If the
target
property has an event handler for the event registered on it, it's ran. - If
bubbles
istrue
, the event is propagated to the direct parent, then the next one and so on until the root element is reached. - If
bubbles
isfalse
, the event isn't propagated to any ancestors.
- If the
- Bubbling
- The exact opposite of capturing occurs; by default, all events are registered in this phase in modern browsers.
- If the direct parent of the clicked element has an event (e.g.
click
) handler registered on it, it's ran. - It moves 'up' to the next immediate ancestor and performs the same task, and so on until it reaches the root element,
<html>
.
- Capturing
-
All JavaScript events go through the capturing and target phases.
-
The event object has a function,
stopPropagation()
, which stops the event from bubbling up the chain.
-
JSON is a (double-quoted) text-based data format that resembles JavaScript object literal format; can only contain properties, and no methods.
JSON.stringify()
skips JS-specific object properties likeSymbol
since JSON is language-independent.
-
JSON prohibits circular references.
-
Like with
toString()
, objects may provide a built-intoJSON()
; CallingJSON.stringify()
implicitly callstoJSON()
if available. -
Both
JSON.parse()
andJSON.stringify()
support optional transform functions which allows smart reading / writing. -
Deserialization - converting a string to a native object.
-
Serialization - converting a native object to a string; it can be transmitted across a network.
- Event handlers are a form of asynchronous programming.
XMLHttpRequest
was an early form of an asynchronous API that used event handlers to perform async operations.- Using callbacks-based asynchronous programming leads to code that's harder to read and debug. It leads to the problem known as callback hell or pyramid of doom.
- To avoid the "callback hell", modern JS uses Promises as the basis for asynchronous programming instead of callbacks.
- A promise is an object that's returned by an asynchronous function; it represents the current state of an async operation, and it can be any of:
- pending - promise created and in the process.
- fulfilled - success;
then()
handler is called. - rejected - failure;
catch()
handler is called.
- The term settled is used to refer to a non-pending state: either fulfilled or rejected.
Note
A promise is resolved if it is settled, or if it has been "locked in" to follow the state of another promise.
- For instance,
fetch()
is the promise-based alternative toXMLHttpRequest
(XHR). - Promises can be chained because
then()
returns a promise itself. - Promise objects also provide a
catch()
method for error handling upon rejection / failure. - To run promises that are independent of one another, we can use:
Promise.all([...promises])
- fulfilled only if all the promises in the array are fulfilled; rejected otherwise.Promise.any([...promises])
- fulfilled as soon as any one of the promises in the array is fulfilled; rejected if all of them are rejected.
- Inserting
async
keyword before a function definition makes it asynchronous. - Inside the
async
function,await
can then be used before a function call that returns a promise. The code waits at this point until the promise is settled, returning a fulfilled / rejected value.
Note
Async functions always return a promise.
async function fetchTodos() {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos"
);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Unable to get todos: ${error}`);
}
}
const promise = fetchTodos();
console.log(promise[0].name); // ⛔
promise.then((data) => console.log(data[0].name)); // ✅
[!example] Example: Implementing a promise-based sleep() function
async function sleep(duration) {
return new Promise((resolve) => {
if (duration < 0) throw new Error("Negative Timer")
setTimeout(resolve, duration)
})
}
(async () => {
console.log('Hi!');
await sleep(5000);
console.log('Bye!');
})()
// 0s: Hi!
// 5s: Bye!
console.log('Hi!');
sleep(5000).then(() => {
console.log('Bye!');
});
[!example] Example: Implementing a promise-based alarm() API
function alarm(person, delay) {
return new Promise((resolve, reject) => {
if (delay < 0) {
throw new Error("Alarm delay must be set to a positive value");
}
setTimeout(() => {
resolve(`Wake up, ${person}!`);
}, delay);
});
}
alarm("Dave", 2000)
.then((message) => (output.textContent = message))
.catch((error) => (output.textContent = `Unable to set alarm: ${error}`));
// using async/await
try {
const message = await alarm("Dave", 2000);
output.textContent = message;
} catch (error) {
output.textContent = `Unable to set alarm: ${error}`;
}
[!example] Example: Implementing the fetch API using Promises &
XMLHttpRequest
const fetchData = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
const status = xhr.status;
if (status === 0 || (status >= 200 && status < 400)) {
resolve(xhr.responseText);
} else {
reject("Error!");
}
}
};
xhr.open("GET", url);
xhr.send();
});
};
fetchData("https://jsonplaceholder.typicode.com/todos")
.then((res) => {
return JSON.parse(res);
})
.then((data) => {
console.log(data);
})
.catch((err) => console.log(err));
- enable us to run tasks in a separate thread; there are 3 different types:
- dedicated / web workers - a simple way to run scripts in the background.
- shared workers - can be accessed from several browsing contexts, e.g. scripts running in different windows or iframes.
- service workers - act as proxy servers; sit between web applications, the browser, and the network.
DOM (Document Object Model)
- ![[DOM]]
- The [[DOM|Document Object Model]] represents the document currently loaded in a browser tab.
- DOM Manipulation
AJAX (Asynchronous JavaScript and XML)
- A general term for sending data asynchronously.
- A technique used by data-driven websites that uses [[HTTP]] requests and [[DOM]] manipulation APIs to update certain parts of a page; This way pages are updated faster (no refresh) and bandwidth is reduced (less data download).
- In the earlier days XHR was used to implement AJAX:
// using try...catch
const request = new XMLHttpRequest();
try {
request.open("GET", "https://jsonplaceholder.typicode.com/todos");
request.responseType = "json";
request.addEventListener("load", () => console.log(request.response));
request.addEventListener("error", () => console.error("XHR error"));
request.send();
} catch (error) {
console.error(`XHR error: ${request.status}`);
}
- The "modern" way of making [[HTTP]] requests:
fetch()
fetch()
is an asynchronous API which returns a Promise.
Canvas API
- Enables drawing graphics thru JS and the
<canvas>
element.
<canvas width="480" height="320">
<p><!-- fallback content --></p>
</canvas>
const canvas = document.querySelector("canvas");
const width = canvas.width;
const height = canvas.height;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.fillRect(0, 0, width, height);
function random(number) {
return Math.floor(Math.random() * (number + 1));
}
function randomRect(x, y) {
ctx.fillStyle = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
ctx.fillRect(x, y, random(width) / 2, random(height) / 2);
}
canvas.addEventListener("click", (e) => {
randomRect(e.clientX, e.clientY);
});
Date Object
new Date()
- A timestamp is an integer number that represents the number of milliseconds passed since
Jan 1st, 1970
; negative values represent dates beforeJan 1st, 1970
. - If a value exceeds its allowed range, the extra value is distributed automatically.
- e.g.
new Date(2022, 13, 34)
->Mar 06, 2023
- e.g.
Date
to number conversion yields the timestamp same asdate.getTime()
.Date.parse(str)
- reads a date from a string.
Client-side Storage
- Cookies
- Web Storage
localStorage
sessionStorage
- IndexedDB API
- Cache API
- The browser contains this and many more powerful web APIs like: Audio API, History, IndexedDB, WebGL.
Third-Party APIs
- REST APIs
- Standard database functions are performed by making [[HTTP]] requests to specific URLs, that include data like search terms encoded in the URL as parameters; these actions can be CRUD operations: creating, reading, updating, or deleting records within a resource.
- Removing event listeners can improve efficiency for complex applications.
- It’s considered a good practice to minimize the use of global variables. But, they can be useful to store project-level data.
- Use comments to describe how and why the code works.
- Use JSDoc syntax to document a function's usage, parameters, and its returned value.
/**
* Returns x raised to the n-th power.
*
* @param {number} x The number to raise.
* @param {number} n The power, must be a natural number.
* @return {number} x raised to the n-th power.
*/
function pow(x, n) {
return x ** n;
}
- It is considered inefficient and a bad practice to pollute your HTML with JavaScript.
<button onclick="clicked()">Click me!</button>
- Use
promise.all
to execute multiple but independent async operations in parallel, rather than sequentially.- This potentially reduces the total time compared to making requests sequentially.
async function fetchData() {
try {
const [userData, productData, orderData] = await Promise.all([
fetch('https://api.example.com/user'),
fetch('https://api.example.com/products'),
fetch('https://api.example.com/orders')
]);
const user = await userData.json();
const products = await productData.json();
const orders = await orderData.json();
return { user, products, orders };
} catch (error) {
console.error('Error fetching data:', error);
}
}
- Not Handling Errors Properly
- Omitting
.catch()
methods for Promises. - Neglecting
try/catch
blocks aroundawait
calls.
- Omitting
- Synchronous Loops with Asynchronous Calls
- Using
await
inside loops, leading to sequential execution instead of concurrent.
- Using
- Forgetting to Mark Functions as
async
- Using
await
in non-async
functions, resulting in syntax errors.
- Using
- Ignoring Performance Considerations
- Creating multiple Promises in a loop without control, leading to performance issues.
- Mixing Patterns
- Combining callbacks, Promises, and
async/await
, which can create confusion and bugs.
- Combining callbacks, Promises, and
- Stick to using Latin characters (0-9, a-z, A-Z) and the underscore character.
- Don't use underscores at the start of variables; it may cause confusion with internal [[JavaScript]] constructs.
- Don't use numbers at the start of variables; this is not allowed.
- Variables are case-sensitive.
- Avoid using JavaScript reserved words as variable names.
- Use
const
when you can, and uselet
when you have to.
- OOP: Classes
- Lexical scoping
- Event Loop
- Function borrowing / explicit binding
eval
- Dynamic imports
- Iterators & Generators
- Reference Type
- Unicode
- Arrays + Objects
- Typed arrays
- Built-in Objects
- Math
- Date
- Error
- Function
- RegExp
- Functional Programming
- [[JavaScript|JS]] Signals
- Web APIs
- Intl - Date Object
- Web Animations API
- GreenSock GSAP + ScrollTrigger
- Web Storage - IndexedDB
- WebRTC
- PeerJS
- WebGL
- Proxy
- Web Authentication API
- Web Workers
- Modern Web APIs
- New Features - New JavaScript features
-
Deep JavaScript (Dr. Axel Rauschmayer)
-
Eloquent JavaScript (Marijn Haverbeke)
-
Human JavaScript (Henrik Joreteg)
-
JavaScript: The Definitive Guide (David Flanagan)
-
JavaScript: The Good Parts (Douglas Crockford)
-
You Don’t Know JS (Kyle Simpson) ⭐