JavaScript Fundamentals · Complete Notes & Examples

Each topic below gives a quick explanation and at least one example. Copy/paste and run in your browser DevTools console (F12 or Ctrl/⌘+Shift+I).

Beginner → Intermediate · ES2023-friendly

PART 1 · An Introduction to JavaScript

JavaScript was initially created to “make web pages alive”.

The programs in this language are called scripts. They can be written right in a web page’s HTML and run automatically as the page loads.

Scripts are provided and executed as plain text. They don’t need special preparation or compilation to run.

JavaScript is able to:

  • Add new HTML to the page, change the existing content, modify styles.
  • React to user actions, run on mouse clicks, pointer movements, key presses.
  • Send requests over the network to remote servers, download and upload files (so-called AJAX
  • Get and set cookies, ask questions to the visitor, show messages.
  • Remember the data on the client-side (“local storage”).

What makes JavaScript unique?

  • Full integration with HTML/CSS.
  • Simple things are done simply.
  • Supported by all major browsers and enabled by default.

Hello, world!

Use a <script> tag to run JavaScript on a page, or open the DevTools console and type code directly.

<script type="text/javascript" >
  alert('Hello, world!');
</script>
In HTML, put complex scripts in external .js files for caching and reuse.
<script type="text/javascript" src="script.js"></script>
        

Code structure: statements, semicolons, comments

Write one statement per line; semicolons are optional but recommended. Use // or /* ... */ for comments.

// Two statements:
const msg = 'JS!'; 
alert(msg);   // shows "JS!"

The modern mode: "use strict" (Should we use it?)

"use strict" enables safer, modern semantics (e.g., forbids accidental globals). Yes—use it at the top of files.

"use strict";
x = 1; // ReferenceError in strict mode

Why using "use strict"?

Enables stricter error checking, improves performance, and prepares for future language features. It’s a good practice to use "use strict" in all your scripts.

Variables

Use let for reassignable bindings, const for constants. Prefer clear, descriptive names.

const TAX_RATE = 0.18;
let subtotal = 100;
let total = subtotal * (1 + TAX_RATE);

Variable naming

  • Use camelCase for multi-word names (e.g., myVariable).
  • Start with a letter, $, or _. Avoid numbers at the beginning.
  • Be descriptive but concise (e.g., itemCount instead of ic).

Data types

A value in JavaScript is always of a certain type. For example, a string or a number.

Primitives: number, string, boolean, null, undefined, bigint, symbol · Reference: object.

typeof 42           // "number"
typeof 'hi'         // "string"
typeof null         // "object" (quirk)
typeof {}           // "object"

Interaction: alert, prompt, confirm

Use alert to show a message.

alert('Heads up!');

Use prompt to ask for input.

const name = prompt('Your name?','');
alert('Hi, ' + name + '!');

Use confirm to ask for a yes/no answer.

const ok = confirm('Continue?');
alert(ok ? 'Proceeding' : 'Stopped');

Type Conversions

Convert explicitly with Number(x), String(x), Boolean(x). Beware: + concatenates if either side is a string.

Number(' 42 ')      // 42
'4' + 2             // "42"  (concat)
'4' - 2             // 2     (numeric)

Basic operators & maths

Standard arithmetic, assignment, and exponentiation **. The unary + coerces to number.

+true     // 1
2 ** 3    // 8 //exponentiation
let x = 2; x *= 3 + 1; // 8 

Arithmetic operators

  • Addition +,
  • Subtraction -,
  • Multiplication *,
  • Division /,
  • Remainder %,
  • Exponentiation **.

Comparisons

Use ===/!== for strict checks (no type coercion). Strings compare lexicographically.

'Z' > 'A'         // true
0 == false        // true
0 === false       // false (strict)

Conditional branching: if, ?

The if(...) statement evaluates a condition in parentheses and, if the result is true, executes a block of code

For example:

          
            if(age >= 18) {
                alert('adult');
            } else {
                alert('minor');
            }
        

You can use the ? operator for a shorter way to write an if statement. For example:

const age = 20;
const label = age >= 18 ? 'adult' : 'minor';

Logical operators: OR / AND / NOT

|| returns the first truthy; && returns the first falsy; ! negates.

'' || 'fallback'     // "fallback"
'hi' && 123          // 123
!0                   // true

Nullish coalescing: ??

Return the right side only when the left is null or undefined (unlike ||, which treats many values as “falsy”).

Historically, the OR || operator was there first. It’s been there since the beginning of JavaScript, so developers were using it for such purposes for a long time. On the other hand, the nullish coalescing operator ?? was added to JavaScript only recently, and the reason for that was that people weren’t quite happy with ||. The important difference between them is that:

  • || returns the first truthy value.
  • ?? returns the first defined value (ignores only null and undefined).
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
  • The nullish coalescing operator ?? provides a short way to choose the first “defined” value from a list.
  • It’s used to assign default values to variables.
  • The operator ?? has a very low precedence, only a bit higher than ? and =, so consider adding parentheses when using it in an expression.
  • It’s forbidden to use it with || or && without explicit parentheses.

Nullish Coalescing (??) in 2 Minutes

Loops: while, for

Loops are a way to repeat the same code multiple times.

while (condition) {
  // code
  // so-called "loop body"
}

do {
  // loop body
} while (condition);

Examples

let i = 0;
while (i < 3) { // shows 0, then 1, then 2
  alert( i );
  i++;
}
let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);
for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2
  alert(i);
}

Breaking the loop

Normally, a loop exits when its condition becomes falsy. But we can force the exit at any time using the special break directive.

let sum = 0;

while (true) {
  let value = +prompt("Enter a number", '');
  if (!value) break; // (*)
  sum += value;

}
alert( 'Sum: ' + sum );

Continue to the next iteration

The continue directive is a “lighter version” of break. It doesn’t stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows).

for (let i = 0; i < 10; i++) {

  // if true, skip the remaining part of the body
  if (i % 2 == 0) continue;

  alert(i); // 1, then 3, 5, 7, 9
}

The switch statement

A switch statement can replace multiple if checks. It gives a more descriptive way to compare a value with multiple variants.

const role = 'admin';
switch (role) {
  case 'user':  /* ... */ break;
  case 'admin': /* ... */ break;
  default:      /* ... */
}

Example

let arg = prompt("Enter a value?");
switch (arg) {
  case '0':
   alert( 'Zero' );
  case '1':
    alert( 'One' );
    break;
  case '2':
    alert( 'Two' );
    break;
  case '3':
    alert( 'Three' );
    break;
  default:
    alert( 'An unknown value' );
}

Functions (declaration, local/outer vars, params, defaults)

Functions are the main “building blocks” of the program. They allow the code to be called many times without repetition. We’ve already seen examples of built-in functions, like alert(message), prompt(message, default) and confirm(question). But we can create functions of our own as well.

Function Declaration

To create a function we can use a function declaration.

It looks like this:

function showMessage() {
  alert( 'Hello everyone!' );
}
showMessage()

The function keyword goes first, then goes the name of the function, then a list of parameters between the parentheses (comma-separated, empty in the example above, we’ll see examples later) and finally the code of the function, also named “the function body”, between curly braces.

Parameters

function showMessage(name) {
  let message = "Hello " + name + ", I'm JavaScript!"; // local variable

  alert( message );
}

showMessage('Saeed'); // Hello, I'm JavaScript!

          

Default values

We can specify the so-called “default” (to use if omitted) value for a parameter in the function declaration, using =:

function showMessage(name = "stranger") {
  let message = "Hello " + name + ", I'm JavaScript!"; // local variable

  alert( message );
}
showMessage("Layla");
showMessage(); 

Returning a value

A function can return a value back into the calling code as the result. The simplest example would be a function that sums two values:

function sum(a, b) {
  return a + b;
}

let result = sum(1, 2);
alert( result ); // 3

Function expressions

In JavaScript, a function is not a “magical language structure”, but a special kind of value. The syntax that we used before is called a Function Declaration:

function sayHi() {
  alert( "Hello" );
}
sayHi(); // Hello 

There is another syntax for creating a function that is called a Function Expression. It allows us to create a new function in the middle of any expression.

For example:
let sayHi = function() {
  alert( "Hello" );
};
sayHi(); // Hello

Here we can see a variable sayHi getting a value, the new function, created as function() { alert("Hello"); }.

Callback function

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "You agreed." );
}

function showCancel() {
  alert( "You canceled the execution." );
}

// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);

Arrow functions

There’s another very simple and concise syntax for creating functions, that’s often better than Function Expressions. It’s called “arrow functions”, because it looks like this:

let func = (arg1, arg2, ..., argN) => expression;

This creates a function func that accepts arguments arg1..argN, then evaluates the expression on the right side with their use and returns its result.

let func = function(arg1, arg2, ..., argN) {
  return expression;
};
function func(arg1, arg2, ..., argN) {
  return expression;
};

Example:

let sum = (a, b) => a + b;

/* This arrow function is a shorter form of:
let sum = function(a, b) {
  return a + b;
};
*/

alert( sum(1, 2) ); // 3
let age = prompt("What is your age?", 18);

let welcome = (age < 18) ?
  () => alert('Hello!') :
  () => alert("Greetings!");

welcome();

Multiline arrow functions

Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a return within them to return a value (just like a regular function does).

let sum = (a, b) => {  // the curly brace opens a multiline function
  let result = a + b;
  return result; // if we use curly braces, then we need an explicit "return"
};

alert( sum(1, 2) ); // 3

PART 2 · Code Quality

Code quality in JavaScript refers to the overall standard and effectiveness of the codebase, encompassing its readability, maintainability, reliability, security, and performance. High-quality code is easier to understand, debug, modify, and extend, leading to more efficient development and reduced technical debt.

Debugging in the browser

Debugging is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools – a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on. We’ll be using Chrome here, because it has enough features, most other browsers have a similar process.

Open DevTools, use breakpoints, console.log, and the Sources panel to step through code.

console.log({value, state});
debugger; // triggers a breakpoint when DevTools is open

Coding Style & Best Practices

Writing clean, readable, and maintainable code is crucial. A consistent style helps prevent bugs and makes collaboration easier.

Variables and Naming

Use const by default to prevent accidental reassignment. Use let only when you know a variable needs to change. Name variables descriptively.

// Good: Clear and immutable where possible
const userProfile = await fetchUserProfile(userId);
let retryCount = 0;

// Avoid: Vague, mutable when it doesn't need to be
var data = ...; // 'var' is legacy, prefer let/const
var i = 0;      // 'i' is not descriptive

Functions

Functions should be small and do one thing well (Single Responsibility Principle). Avoid deep nesting, which makes code hard to follow.

// Good: Clear, single purpose
function isUserAdmin(user) {
  return user.role === 'admin';
}

// Avoid: Too many nested levels
function processData(data) {
  if (data) {
    if (data.items) {
      // ... more nesting
    }
  }
}

Tooling for Consistency

Automated tools can format your code and catch common errors, ensuring consistency across your project.

  • Prettier: An opinionated code formatter that enforces a consistent style.
  • ESLint: A static analysis tool to find and fix problems in your JavaScript code.

PART 3 · Advanced Topics

Array Methods

Arrays come with a host of built-in methods to make common operations like iteration, transformation, and filtering concise and readable.

Iteration: .forEach()

Executes a provided function once for each array element. It's a modern replacement for a simple for loop.

const fruits = ['apple', 'banana', 'cherry'];
fruits.forEach(fruit => {
  alert(`I love ${fruit}s!`);
});
// Logs: "I love apples!", "I love bananas!", "I love cherrys!"

Transformation: .map()

Creates a new array populated with the results of calling a provided function on every element in the calling array.

const numbers = [1, 4, 9, 16];
const roots = numbers.map(num => Math.sqrt(num));
// roots is now [1, 2, 3, 4]
alert(roots);
// numbers is still [1, 4, 9, 16]
alert(numbers);

Filtering: .filter()

Creates a new array with all elements that pass the test implemented by the provided function.

const ages = [32, 33, 16, 40];
const adults = ages.filter(age => age >= 18);
alert(adults);
// adults is now [32, 33, 40]

Aggregation: .reduce()

Executes a "reducer" function on each element of the array, resulting in a single output value (e.g., a sum or a flattened array).

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((accumulator, currentValue) => {
  return accumulator + currentValue;
}, 0); // 0 is the initial value for the accumulator
alert(sum);
// sum is 10

Finding Elements: .find() & .some()

.find() returns the first element that satisfies a condition, while .some() returns a boolean if at least one element satisfies it.

const inventory = [ { name: 'apples', quantity: 2 }, { name: 'bananas', quantity: 0 }];
const appleItem = inventory.find(item => item.name === 'apples'); // { name: 'apples', quantity: 2 }
const hasOutOfStock = inventory.some(item => item.quantity === 0); // true
alert(hasOutOfStock);

Map and Set

Map and Set are modern, specialized data structures that offer more flexibility and performance for certain tasks compared to plain Objects and Arrays.

Map

A Map is a collection of keyed data items, much like an Object. However, the main difference is that Map allows keys of any type and maintains the insertion order.

const userRoles = new Map();

// Set key-value pairs
const john = { name: 'John' };
userRoles.set(john, 'Admin');
userRoles.set('guest', 'Limited Access');

// Get a value by key
alert(userRoles.get(john)); // "Admin"

// Check for existence
  alert(userRoles.has('guest')); // true

alert(userRoles.size); // 2

Set

A Set is a special type of collection that only stores unique values. If you add a value that already exists, it will be ignored.

const uniqueNumbers = new Set();
uniqueNumbers.add(1);
uniqueNumbers.add(5);
uniqueNumbers.add(5); // This duplicate is ignored
uniqueNumbers.add(2);

// A common use case: remove duplicates from an array
const numbers = [2, 3, 4, 4, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7];
const unique = [...new Set(numbers)]; // [2, 3, 4, 5, 6, 7];

alert(unique);
alert(unique.length); // 6

Objects

Objects store keyed collections of data and more complex entities. Unlike primitives (single values), objects are mutable containers of properties (key–value pairs). Keys are strings or symbols; values can be anything.

Create objects

let user1 = new Object();   // constructor syntax
let user2 = {};             // literal syntax (common)

Literals & properties

let user = {
  name: "John",
  age: 30
};
// read
alert(user.name); // "John"
alert(user.age);  // 30
// add
user.isAdmin = true;
// delete
delete user.age;

Multiword keys & bracket notation

Dot notation needs valid identifiers. Use brackets for any string key or when key comes from a variable.

let user = {};
user["likes birds"] = true;
alert(user["likes birds"]); // true

let key = "likes birds";
alert(user[key]); // true

user.name = "John";
// Dot notation cannot use a variable name as a key:
let k = "name";
alert(user.k); // undefined (looks for literal 'k')
alert(user[k]);

Computed properties

Build keys dynamically inside object literals.

let fruit = "apple";
let bag = {
  [fruit]: 5,              // key becomes "apple"
  [fruit + "Computers"]: 2 // key becomes "appleComputers"
};
alert(bag.apple);           // 5
alert(bag.appleComputers);  // 2

Property value shorthand

function makeUser(name, age){
  return { name, age }; // same as { name: name, age: age }
}
const u = makeUser("John", 30);
alert(u.name); // "John"

Property name rules

Object property names can be (almost) any string—even reserved words or numeric-looking strings.

let obj = { for: 1, let: 2, return: 3, 0: "zero" };
alert(obj.for + obj.let + obj.return); // 6
alert(obj[0]); // "zero" "zero"
alert(obj["0"]); // "zero" "zero"
Note: __proto__ is special; assigning non-objects to it won’t behave like normal props.

Check property existence

Reading a missing property returns undefined. Use "key" in obj when undefined could be a valid value.

let user = {};
alert(user.missing === undefined); // true

let obj2 = { test: undefined };
alert("test" in obj2); // true (property exists)

Iterate properties: for...in

let user = { name: "John", age: 30, isAdmin: true };
for (let key in user) {
  alert(key + ' ' + user[key]);
}

Property order

Integer-like keys iterate in ascending numeric order; other keys iterate in creation order.

let codes = { "49": "Germany", "41": "Switzerland", "1": "USA" };
for (let code in codes) alert(code); // 1, 41, 49

let user3 = { name: "John", surname: "Smith" };
user3.age = 25;
for (let k in user3) alert(k); // name, surname, age

Summary

  • Objects = key–value stores; keys are strings/symbols, values are any type.
  • Access via dot (obj.key) or brackets (obj["key"]); brackets allow variables & any string.
  • Manage props: obj.key = v, delete obj.key, test with "key" in obj.
  • Iterate with for...in. Integer-like keys are ordered numerically; others by insertion order.

Object.keys / values

These methods are used to retrieve different aspects of an object's properties.

const person = {
  name: 'Alice',
  age: 30,
  city: 'New York'
};

// Object.keys returns an array of keys
const keys = Object.keys(person);
alert(keys); // ['name', 'age', 'city']

// Object.values returns an array of values
const values = Object.values(person);
alert(values); // ['Alice', 30, 'New York']

 
      

Date & Time (Summary)

JavaScript represents date/time with the built-in Date object. A Date always has both date and time. Months are 0–11 (Jan=0). getDay() returns 0–6 (Sun=0). Dates “auto-correct” when you set out-of-range parts (useful for adding days/months).

Create dates

// Current date/time
let now = new Date();
alert(now);

// From timestamp (ms since Jan 1, 1970 UTC)
let jan01_1970 = new Date(0);
alert(jan01_1970);

let jan02_1970 = new Date(24 * 3600 * 1000);
alert(jan02_1970);

// Before 1970: negative timestamps
let dec31_1969 = new Date(-24 * 3600 * 1000);
alert(dec31_1969);

// From ISO-like string (parsed similar to Date.parse)
let d1 = new Date("2017-01-26");
alert(d1);

// From parts (year, month(0-11), date, hours, minutes, seconds, ms)
let d2 = new Date(2011, 0, 1, 2, 3, 4, 567);
alert(d2);

Read components (local time)

let d = new Date();
alert(d.getFullYear());   // e.g., 2025
alert(d.getMonth());      // 0..11
alert(d.getDate());       // 1..31
alert(d.getDay());        // 0..6 (Sun..Sat)
alert(d.getHours());      // 0..23
alert(d.getMinutes());    // 0..59
alert(d.getSeconds());    // 0..59
alert(d.getMilliseconds());// 0..999

// UTC variants: getUTCFullYear(), getUTCMonth(), getUTCHours()...
alert(d.getUTCHours());   // hour in UTC

Set components (auto-correct applies)

let today = new Date();

// Change just the hour
today.setHours(0);
alert(today);

// Set exact time to 00:00:00.000
today.setHours(0, 0, 0, 0);
alert(today);

Autocorrection (handy for adding time)

let date = new Date(2013, 0, 32); // Jan 32, 2013 ?!
alert(date); // auto-corrects to Feb 1, 2013

let leap = new Date(2016, 1, 28);       // Feb 28, 2016
leap.setDate(leap.getDate() + 2);       // add 2 days
alert(leap); // Mar 1, 2016

Timestamps & differences

let start = new Date();
// do some work
for (let i = 0; i < 100000; i++) { let x = i * i * i; }
let end = new Date();

alert("The loop took " + (end - start) + " ms"); // subtracting dates = ms diff
alert(+end); // number conversion → timestamp (same as end.getTime())

Fast now (no Date object)

let t1 = Date.now(); // ms since Jan 1, 1970
for (let i = 0; i < 100000; i++) { let x = i * i * i; }
let t2 = Date.now();
alert("The loop took " + (t2 - t1) + " ms");

Parsing strings

// ISO-like format: YYYY-MM-DDTHH:mm:ss.sssZ (Z or ±hh:mm)
let ms = Date.parse("2012-01-26T13:51:50.417-07:00");
alert(ms);                        // timestamp (ms)
let parsed = new Date(ms);        // make Date from it
alert(parsed);

Key points

  • Date always includes both date and time.
  • Months are zero-based; getDay(): Sun=0..Sat=6.
  • Setting out-of-range parts auto-adjusts the date.
  • Subtract Dates (or use Date.now()) to measure durations.

JSON methods & toJSON (Summary)

JSON is a data format for exchanging structured data. In JS, use JSON.stringify to serialize values → string, and JSON.parse to read them back. JSON supports objects, arrays, strings, numbers, booleans, and null. (Functions, undefined, and symbol properties are skipped.)

Stringify basics

let student = {
  name: "John",
  age: 30,
  isAdmin: false,
  courses: ["html","css","js"],
  spouse: null
};

let json = JSON.stringify(student);   // → string
alert(typeof json);                   // "string"
alert(json);
/* Example output:
{"name":"John","age":30,"isAdmin":false,"courses":["html","css","js"],"spouse":null}
*/

Primitives also stringify

alert(JSON.stringify(1));           // 1
alert(JSON.stringify("test"));     // "test"
alert(JSON.stringify(true));       // true
alert(JSON.stringify([1,2,3]));    // [1,2,3]

Skipped in JSON

let user = {
  sayHi(){ alert("Hello"); },   // function → skipped
  [Symbol("id")]: 123,          // symbol → skipped
  something: undefined          // undefined → skipped
};
alert(JSON.stringify(user));     // "{}"

Nested objects (ok) & circular refs (error)

let room = { number: 23 };
let meetup = { title: "Conference", place: room };
room.occupiedBy = meetup; // circular link
try {
  JSON.stringify(meetup);
} catch (e) {
  alert("Error: " + e.message);  // Converting circular structure to JSON
}

Filtering/transforming: replacer

Use an array of allowed keys, or a function (key, value) => newValue.

let room2 = { number: 23 };
let meetup2 = {
  title: "Conference",
  participants: [{name:"John"},{name:"Alice"}],
  place: room2
};
room2.occupiedBy = meetup2;

// Keep only selected keys everywhere:
alert(JSON.stringify(meetup2, ["title","participants","place","name","number"]));
/*
{"title":"Conference","participants":[{"name":"John"},{"name":"Alice"}],"place":{"number":23}}
*/

// Or use a replacer function to drop circular ref:
alert(JSON.stringify(meetup2, function replacer(key, value){
  if (key === "occupiedBy") return undefined;
  return value;
}));

Pretty printing: space

let userPretty = { name:"John", age:25, roles:{ isAdmin:false, isEditor:true } };
let pretty = JSON.stringify(userPretty, null, 2); // 2-space indent
alert(pretty);
/* 
{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}
*/

Custom toJSON

let room3 = {
  number: 23,
  toJSON(){ return this.number; } // define your own JSON form
};
let meetup3 = { title: "Conference", room: room3 };

alert(JSON.stringify(room3));   // 23
alert(JSON.stringify(meetup3)); // {"title":"Conference","room":23}

Parsing strings: JSON.parse

let numbers = "[0,1,2,3]";
let arr = JSON.parse(numbers);
alert(arr[1]); // 1

let userData = '{"name":"John","age":35,"isAdmin":false,"friends":[0,1,2,3]}';
let userObj = JSON.parse(userData);
alert(userObj.friends[1]); // 1

Key points

  • JSON.stringify(value, replacer?, space?) → string.
  • JSON.parse(str, reviver?) → value; use reviver to restore types like Date.
  • If an object has toJSON, it’s used automatically during stringify.

Scheduling (Summary): setTimeout & setInterval

Schedule code to run later. setTimeout(fn, ms) runs once after ms. setInterval(fn, ms) repeats every ms until canceled. Delays are approximate (CPU load, background tabs, battery saver, etc.).

Run once: setTimeout


let tId = setTimeout(() => alert("John"), 1000); // 1s later → "Hi, John!"
 

Repeat: setInterval (and stop)

let ticks = 0;
let iId = setInterval(() => {
  alert("tick " + (++ticks));
  if (ticks === 3) { clearInterval(iId); alert("stop"); }
}, 1500); // every 1.5s

“ASAP” after current script

setTimeout(() => alert("World")); // queued
alert("Hello");                    // shows first
Notes: After ~5 nested timers, browsers enforce a ≥4ms minimum. Don’t pass strings (e.g. "alert('x')"); pass functions.

Function Binding (Short Summary)

When we pass object methods (e.g. to setTimeout), this can be lost — it no longer refers to the original object. bind() fixes that by locking the function’s this value.

Lost this

let user = {
  firstName: "John",
  sayHi() { alert("Hi, " + this.firstName); }
};

setTimeout(user.sayHi, 1000); // ❌ "Hi, undefined"

Fix 1: wrapper function

let user = {
  firstName: "John",
  sayHi() { alert("Hi, " + this.firstName); }
};

setTimeout(() => user.sayHi(), 1000); // ✅ "Hi, John"

Fix 2: bind()

let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John
  • bind(thisArg) fixes the value of this.
  • Use bind when passing methods as callbacks.

Error handling (Summary): try...catch

Use try...catch to prevent crashes on runtime errors and handle them gracefully. Optional finally runs whether an error happened or not.

Basic pattern

try {
  alert("Start");
  lalala;                 // runtime error
  alert("Never runs");
} catch (err) {
  alert(err.name + ": " + err.message); // e.g., "ReferenceError: lalala is not defined"
} finally {
  alert("Cleanup always runs");
}

Throw your own errors

let json = '{ "age": 30 }';
try {
  let user = JSON.parse(json);
  if (!user.name) throw new SyntaxError("Incomplete data: no name");
  alert("User: " + user.name);
} catch (err) {
  alert("JSON Error: " + err.message);
}

Promises (Short Summary)

A Promise links slow “producing” code to “consuming” code. It starts pending and settles once: fulfilled via resolve or rejected via reject. Use .then for success, .catch for errors, and .finally for cleanup.

Create

let p = new Promise((resolve, reject) => {
  // async work...
  setTimeout(() => resolve("done"), 500); // or: reject(new Error("oops"))
});

Consume

p.then(result => alert(result))         // "done"
 .catch(err => alert(err))              // "Error: oops"
 .finally(() => alert("finished"));     // always runs

One result only

new Promise(r => { r("A"); r("B"); })  // "B" is ignored

Handlers can be added anytime

let ready = Promise.resolve("OK");
ready.then(v => alert(v)); // alerts immediately

Mini example: load script

function loadScript(src) {
  return new Promise((resolve, reject) => {
    const s = document.createElement("script");
    s.src = src;
    s.onload = () => resolve(s);
    s.onerror = () => reject(new Error("Load error: " + src));
    document.head.append(s);
  });
}

loadScript("https://example.com/app.js")
  .then(s => alert(s.src + " loaded"))
  .catch(e => alert(e.message))
  .finally(() => alert("done"));


loadScript("https://portal.almasar101.com/wp-includes/js/jquery/ui/core.min.js?ver=1.13.3")
  .then(s => alert(s.src + " loaded"))
  .catch(e => alert(e.message))
  .finally(() => alert("done"));



Watch Promise Video Explanation Arabic
Watch Promise Video Explanation English

Promise chaining

Chaining promises allows you to perform sequential asynchronous operations. Each .then handler receives the result of the previous one, and you can return a new value or a promise to pass it to the next handler.

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

Error Handling with Promises (Short Summary)

.catch handles any rejection or error in a promise chain — even those thrown in .then handlers. You can place one .catch at the end to handle all errors above.

Basic example

fetch('https://no-such-server.example')
  .then(r => r.json())
  .catch(err => alert("Error: " + err.message));

Implicit try...catch

new Promise(() => { throw new Error("Whoops!"); })
  .catch(err => alert(err)); // works same as reject()

Rethrowing

new Promise(() => { throw new Error("Whoops!"); })
  .catch(e => {
    if (e instanceof URIError) alert("Handled");
    else throw e; // pass to next .catch
  })
  .catch(e => alert("Final catch: " + e));

Unhandled rejections

window.addEventListener("unhandledrejection", e => {
  alert("Unhandled: " + e.reason);
});

new Promise(() => { throw new Error("Whoops!"); });
  • .catch catches rejections or thrown errors.
  • Use throw inside .catch to rethrow unhandled ones.
  • Browsers trigger unhandledrejection if a rejected promise has no .catch.

Promise API (Short Summary)

The Promise class has 6 static methods to combine or create promises. They help run tasks in parallel, handle multiple results, or settle early.

1️⃣ Promise.all

Waits for all promises to resolve → returns array of results. Rejects immediately if any fail.

Promise.all([
  fetch("https://api.github.com/users/iliakan"), fetch("https://api.github.com/users/zeiadhabbab")
]).then(all => alert("All done"))
  .catch(err => alert("Error: " + err));

2️⃣ Promise.allSettled

Waits for all to finish (fulfilled or rejected). Always resolves with an array of status objects.

Promise.allSettled([
  fetch("https://api.github.com/users/zeiadhabbab"), fetch("bad.json")
]).then(r => alert(JSON.stringify(r)));

3️⃣ Promise.race

Resolves/rejects with the first settled promise (winner of the “race”).

Promise.race([
  new Promise(r => setTimeout(() => r("fast"), 500)),
  new Promise(r => setTimeout(() => r("slow"), 1500))
]).then(alert); // "fast"

4️⃣ Promise.any

Returns the first fulfilled result. If all fail → rejects with AggregateError.

Promise.any([
  Promise.reject("fail"),
  Promise.resolve("ok")
]).then(alert); // "ok"

5️⃣ Promise.resolve

Creates a resolved promise with a given value.

Promise.resolve("ready").then(alert);

6️⃣ Promise.reject

Creates a rejected promise with an error.

Promise.reject(new Error("Oops")).catch(e => alert(e.message));
💡 Most used: Promise.all – run tasks in parallel and wait for all results.

Async / Await (Short Summary)

async and await make working with promises simpler and more readable. async makes a function return a promise. await pauses inside that function until the promise settles.

Async functions

async function f() {
  return 1; // returns Promise.resolve(1)
}
f().then(alert); // 1

Await keyword

async function f() {
  let promise = new Promise(r => setTimeout(() => r("done!"), 1000));
  let result = await promise; // waits 1s
  alert(result); // "done!"
}
f();

Error handling

async function f() {
  try {
    let r = await fetch("https://no-site.example");
  } catch (err) {
    alert("Error: " + err.message);
  }
}
f();

Await + Promise.all

async function loadAll() {
  let [a, b] = await Promise.all([
    fetch("https://api.github.com/users/zeiadhabbab"),
    fetch("https://api.github.com/users/zeiadhabbab")
  ]);
  alert("Both loaded!");
}
loadAll();
  • async → always returns a Promise.
  • await → waits for the promise and gives its result.
  • Use try...catch to handle errors inside async functions.
  • Combine with Promise.all() for parallel tasks.
💡 Think of: await = “pause until ready”, async = “function that always returns a promise”.

Watch Async / Await Video Explanation Arabic

Watch Async / Await Video Explanation

PART 4 · DOM

The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page structure as a tree of objects, allowing JavaScript to dynamically access and update content, structure, and styles.

This section covers the basics of DOM manipulation, including traversing the DOM tree, searching for elements, modifying content and attributes, handling styles and classes, and working with events.

Browser environment & specs

The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. A platform may be a browser, or a web-server or another host, or even a “smart” coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a host environment. A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.

There’s a “root” object called window. It has two roles:

function sayHi() {
  alert("Hello");
}

// global functions are methods of the global object:
window.sayHi();

alert(window.innerHeight); // inner window height

DOM (Document Object Model)

The Document Object Model, or DOM for short, represents all page content as objects that can be modified. The document object is the main “entry point” to the page. We can change or create anything on the page using it.

// change the background color to red
document.body.style.background = "red";

// change it back after 1 second
setTimeout(() => document.body.style.background = "", 1000);

BOM (Browser Object Model)

The Browser Object Model (BOM) is a collection of objects that provide browser-specific features. It includes objects like window, navigator, location, history, and screen.

alert(location.href); // shows current URL
if (confirm("Go to Wikipedia?")) {
  location.href = "https://wikipedia.org"; // redirect the browser to another URL
}

DOM tree

The DOM is a tree of nodes representing the document structure.

<html>
    <head>
        <title>My title</title>
    </head>

    <body>
        <a href="#">My link</a>
        <h1>My header</h1>
    </body>
</html>
DOM Tree Example

https://software.hixie.ch/utilities/js/live-dom-viewer/

Walking the DOM

To navigate the DOM tree, we can use properties like parentElement, children, firstElementChild, lastElementChild, nextElementSibling, and previousElementSibling.

const list = document.getElementById('list');
list.firstElementChild.textContent; // "A"
<html> = document.documentElement
<body>  = document.body
<head>  = document.head

Example

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>Information</li>
  </ul>

  <div>End</div>

  <script>
    for (let i = 0; i < document.body.childNodes.length; i++) {
      alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
    }
  </script>
  ...more stuff...
</body>
</html>

Difference between Node object and Element object?

Node object: represents any part of the document tree, like elements, text, comments, etc.
Element object: specifically represents HTML elements and provides methods and properties for manipulating them.

Searching: getElement*, querySelector*

To search for elements in the DOM, we can use methods like getElementById, getElementsByTagName, getElementsByClassName, and querySelector (and its plural querySelectorAll).

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // get the element
  let elem = document.getElementById('elem');

  // make its background red
  elem.style.background = 'red';
</script>


        

Also, there’s a global variable named by id that references the element:

<div id="myDiv">Hello</div>
<script>
  alert(myDiv.textContent); // Hello
</script>

querySelectorAll

To select multiple elements, we can use querySelectorAll. It returns a collection of elements that match the given selector.

<ul>
  <li>The</li>
  <li>test</li>
</ul>
<ul>
  <li>has</li>
  <li>passed</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');

  for (let elem of elements) {
    alert(elem.innerHTML); // "test", "passed"
  }
</script>

querySelector

To select a single element, we can use querySelector. It returns the first element that matches the given selector.

<div class="note">First note
</div>
<div class="note">Second note
</div>
<script>
  let firstNote = document.querySelector('.note');
  alert(firstNote.innerHTML); // "First note"
</script>

Node properties: type, tag, contents

Every element, text, or comment in a web page is a DOM node. Each node belongs to a class in the DOM hierarchy that defines its properties and methods.

1. DOM Node Classes

The DOM is built on a class hierarchy:

  • EventTarget – Base class that enables events.
  • Node – Adds tree structure features (parentNode, childNodes).
  • Element – Adds element-level features like children and querySelector().
  • HTMLElement – Base class for all HTML tags (<div>, <a>, etc.).
  • Specific classes like HTMLInputElement, HTMLBodyElement, etc., add tag-specific features.

Example:

let input = document.querySelector('input');
alert(input.constructor.name); // HTMLInputElement

2. Identifying Node Type

The nodeType property tells what kind of node you’re working with:

  • 1 → Element
  • 3 → Text
  • 9 → Document
alert(document.body.nodeType); // 1
alert(document.body.firstChild.nodeType); // 3

3. Tag Name

You can get a node’s tag using tagName or nodeName:

alert(document.body.tagName); // BODY
alert(document.body.nodeName); // BODY

tagName works only for elements, while nodeName also works for text or comment nodes.

4. innerHTML and outerHTML

innerHTML lets you read or replace the HTML inside an element.

document.body.innerHTML = "<h1>Welcome!</h1>";

outerHTML includes the element itself. Writing to it replaces the element in the DOM.

div.outerHTML = "<p>Replaced!</p>";

5. textContent and nodeValue

Use textContent to safely get or set plain text (no HTML tags). Use nodeValue or data to access the content of text or comment nodes.

elem.textContent = "<b>Safe text</b>"; // shows literally, no bold

6. Hidden Property

The hidden attribute quickly hides elements, like display:none in CSS.

elem.hidden = true;

7. Common Node Properties

  • value → for inputs
  • href → for links
  • id → for any element

Summary

  • Each DOM node belongs to a class and inherits properties from its hierarchy.
  • nodeType, tagName, and nodeName help identify nodes.
  • innerHTML and outerHTML handle HTML markup.
  • textContent and data handle plain text safely.
  • hidden quickly toggles visibility.
💡 Tip: Use textContent to safely insert text, and console.dir(element) to inspect all its DOM properties.
const p = document.createElement('p');
p.nodeType;   // 1 = ELEMENT_NODE
p.tagName;    // "P"

Attributes & properties

When the browser parses HTML, most standard attributes on elements become JavaScript DOM properties. But they aren’t always identical, and not every attribute becomes a property.

1) DOM Properties (JS side)

  • Live on the element object (regular JS object).
  • Typed values (e.g., checked is boolean, style is an object).
  • Case-sensitive names: use elem.nodeType, not elem.NoDeTyPe.
  • You can add your own: document.body.myData = {name:'Caesar'}.
// Custom property
document.body.myData = { name: 'Caesar', title: 'Imperator' };
console.log(document.body.myData.title); // Imperator

// Prototype extension (affects all elements)
Element.prototype.sayHi = function () {
  console.log(`Hello, I'm <${this.tagName.toLowerCase()}>`);
};
document.body.sayHi(); // Hello, I'm <body>

2) HTML Attributes (markup side)

  • What you write in HTML.
  • Names are case-insensitive; values are always strings.
  • Some attributes are standard only for certain tags (e.g., type is standard on <input>, not on <body>).
<div id="card" about="Elephant"></div>
<script>
  const el = document.getElementById('card');
  console.log(el.getAttribute('About')); // "Elephant" (case-insensitive)
  el.setAttribute('data-note', 123);
  for (const a of el.attributes) {
    console.log(a.name, '=', a.value);
  }
</script>

3) Working with Attributes (raw HTML values)

  • elem.hasAttribute(name)
  • elem.getAttribute(name)
  • elem.setAttribute(name, value)
  • elem.removeAttribute(name)
  • elem.attributes → iterable list of all attributes

4) Sync Rules (Attribute ⇄ Property)

Standard attributes usually sync with their properties, but not always both ways:

  • Two-way (usually): id
    input.setAttribute('id', 'x'); console.log(input.id); // "x"
    input.id = 'y'; console.log(input.getAttribute('id')); // "y"
  • One-way: value (attribute → property only)
    input.setAttribute('value', 'text'); console.log(input.value); // "text"
    input.value = 'new'; console.log(input.getAttribute('value')); // "text" (unchanged original)
  • Typed differences: checked is boolean vs attribute is string/empty
    <input type="checkbox" checked id="c">
    c.getAttribute('checked'); // ""  (string)
    c.checked;                 // true (boolean)
  • Normalized properties: a.href is always absolute
    <a id="link" href="#hash">
    link.getAttribute('href'); // "#hash"
    link.href;                 // "https://example.com/page#hash"
  • Objects: style attribute is string; elem.style is CSS object
    div.getAttribute('style'); // "color:red;font-size:120%"
    div.style.color;          // "red"

5) Custom Data: data-* & dataset

Use data-* for safe, conflict-free custom attributes. Access them via elem.dataset (camelCase for multi-word).

<div id="order" class="order" data-order-state="new">A new order</div>
<script>
  console.log(order.dataset.orderState); // "new"
  order.dataset.orderState = 'pending';  // updates attribute and can drive CSS
</script>

/* CSS */
.order[data-order-state="pending"] { color: blue; }

6) Quick Guidance

  • Prefer properties for day-to-day JS (typed, live state).
  • Use attributes when you need the exact HTML value (getAttribute) or non-standard/custom data.
  • For custom data, use data-* + dataset.
  • Remember special sync cases (e.g., value).
💡 Rule of thumb: Read/write current UI state via properties. Read/write the original markup via attributes.

Modifying the Document (DOM)

Create elements, insert them in precise positions, clone or remove nodes, and safely inject HTML or text.

1) Create Nodes

  • document.createElement(tag) → element node
  • document.createTextNode(text) → text node
// Build: <div class="alert"><strong>Hi!</strong> You’ve got a message.</div>
const box = document.createElement('div');
box.className = 'alert';
box.innerHTML = '<strong>Hi!</strong> You’ve got a message.';

2) Insert Nodes (Modern)

  • parent.append(...nodes|strings) – end of parent
  • parent.prepend(...) – start of parent
  • node.before(...) / node.after(...) – around node
  • node.replaceWith(...) – replace node
document.body.prepend(box);             // show at top
box.after(' (inline text)', document.createElement('hr'));

Strings are inserted as text (escaped), not HTML.

3) Insert as HTML

Use elem.insertAdjacentHTML(where, html) for literal HTML.

  • "beforebegin" | "afterbegin" | "beforeend" | "afterend"
box.insertAdjacentHTML('beforeend', '<em> Read this.</em>');

4) Remove / Move

  • node.remove() – delete node
  • Re-inserting a node moves it automatically (no manual remove needed)
setTimeout(() => box.remove(), 1500);

5) Clone

  • elem.cloneNode(true) – deep clone (with children)
  • elem.cloneNode(false) – shallow clone
const copy = box.cloneNode(true);
copy.querySelector('strong').textContent = 'Bye!';
box.after(copy);

6) Batching

Build lists off-DOM, then append once. Either: DocumentFragment or just collect nodes in an array and spread.

// Array approach (simple, fast)
const items = Array.from({length: 3}, (_, i) => {
  const li = document.createElement('li');
  li.textContent = i + 1;
  return li;
});
document.querySelector('#list').append(...items);

7) Legacy Methods (know them, don’t start with them)

appendChild, insertBefore, replaceChild, removeChild exist for older codebases.

8) ⚠️ Avoid document.write

Only works during initial parsing; calling later wipes the page. Prefer the modern APIs above.

💡 Cheat sheet: Build with createElement → insert with append/prepend/before/after → HTML string? use insertAdjacentHTML → batch many nodes → clone with cloneNode → remove with remove().

Styles & Classes

Prefer CSS classes for presentation. Use inline style from JS only when values are dynamic (e.g., computed positions).

1) Prefer Classes over Inline Styles

/* CSS */
.btn { padding: .5rem 1rem; background: #0a7; color: #fff; }

/* JS: add the class instead of manual inline styling */
elem.classList.add('btn');

2) className vs classList

  • elem.className → full class string (overwrites all).
  • elem.classList → add/remove/toggle one class at a time.
// className (replace all)
elem.className = 'card card--primary';

// classList (surgical)
elem.classList.add('is-active');
elem.classList.remove('is-hidden');
elem.classList.toggle('dark');
elem.classList.contains('dark'); // true/false

// Iterate classes
for (const c of elem.classList) console.log(c);

3) Inline Styles (when you must)

Use camelCase for multi-word properties. Remember units (px, %, etc.).

elem.style.backgroundColor = 'gold';
elem.style.zIndex = '10';
elem.style.left = leftPx + 'px';
elem.style.top  = topPx + 'px';

Reset or Remove an Inline Style

// reset one property
elem.style.display = '';                 // restores CSS/UA default
elem.style.removeProperty('background'); // remove explicitly-set inline style

// set many at once (replaces all inline styles!)
elem.style.cssText = `
  color: red !important;
  background-color: #ffe;
  padding: 8px;
`;
💡 Cheat sheet: Style with CSS → toggle via classList → inline style only for dynamic numbers/positions → reset with '' or removeProperty → read final values with getComputedStyle.

Introduction to Browser Events

An event signals that something happened (click, key press, form submit, etc.). Handlers are functions that run in response.

Common Event Types

  • Mouse: click, contextmenu, mouseover/mouseout, mousedown/mouseup, mousemove
  • Keyboard: keydown, keyup
  • Form: submit, focus
  • Document: DOMContentLoaded
  • CSS: transitionend

Ways to Attach Handlers

  1. HTML attribute (quick demos; avoid for real apps):
    <input type="button" value="Click" onclick="sayHi()">
  2. DOM property (one handler per event):
    button.onclick = function () { alert('Thanks'); };
    button.onclick = null; // remove
  3. addEventListener (multiple handlers + options):
    function handler(e){ console.log(e.type); }
    button.addEventListener('click', handler, { once: true });
    button.removeEventListener('click', handler);
    Use for events like DOMContentLoaded (not available via onDOMContentLoaded).

The Event Object

button.addEventListener('click', function (event) {
  console.log(event.type);               // "click"
  console.log(event.currentTarget === this); // true (except in arrow functions)
  console.log(event.clientX, event.clientY); // mouse coords
});

In HTML attributes, event is also available: onclick="alert(event.type)".

Gotchas & Tips

  • Assign the function, don’t call it: btn.onclick = sayHi (✅), not sayHi() (❌).
  • removeEventListener needs the same function reference used to add.
  • Prefer addEventListener for composability and special events.
  • Use options: { once: true } auto-removes, { passive: true } for scroll/touch (no preventDefault()), { capture: true } to handle in capture phase.
💡 Cheat sheet: Use addEventListener → keep a named handler for removal → read details from event → avoid inline HTML handlers in production.

PART 5 · Network Requests

Network requests allow web apps to fetch data from servers.

Commonly used for fetching JSON data from APIs.

Fetch API is modern, Promise-based; supports JSON, FormData, Blobs, etc.

Fetch

fetch() lets JavaScript make network requests without reloading the page (a.k.a. “AJAX”). Use it to submit forms, load user data, or grab live updates.

Quick start

// Basic GET (2-stage: headers → body)
const res = await fetch('/api/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json(); // or .text() / .blob() / .arrayBuffer() / .formData()
Body can be read once. Choose one: .json(), .text(), .blob(), etc.

Response essentials

  • res.status → HTTP status (e.g. 200, 404)
  • res.oktrue for 200–299
  • res.headers → Map-like headers (get(), iterable)
console.log(res.headers.get('content-type'));
for (const [k, v] of res.headers) console.log(k, v);

Sending requests

JSON POST

const payload = { name: 'John', surname: 'Smith' };
const res = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json;charset=utf-8' },
  body: JSON.stringify(payload)
});
const result = await res.json();

Binary / FormData

// Blob (e.g., from canvas.toBlob or a file input)
const res = await fetch('/upload', { method: 'POST', body: myBlob });
// FormData auto-sets headers (don’t set Content-Type manually)
const fd = new FormData();
fd.append('avatar', fileInput.files[0]);
const res2 = await fetch('/upload', { method: 'POST', body: fd });

Error handling (robust pattern)

async function httpJson(url, opts = {}) {
  const res = await fetch(url, opts);
  // Network errors throw; non-2xx do NOT → check .ok yourself
  if (!res.ok) {
    const msg = await res.text().catch(() => '');
    throw new Error(`HTTP ${res.status} ${res.statusText} – ${msg}`);
  }
  const ct = res.headers.get('content-type') || '';
  return ct.includes('application/json') ? res.json() : res.text();
}

try {
  const data = await httpJson('https://api.github.com/users/iliakan');
  console.log(data);
} catch (err) {
  console.error(err);
}

Headers

Set request headers via headers. Some are forbidden (e.g., Content-Length, Origin, Referer, Cookie, Sec-*), the browser manages those.

await fetch('/secure', { headers: { Authorization: 'Bearer <token>' } });

Common pitfalls

  • Non-2xx statuses don’t throw → always check res.ok.
  • Read the body once; choose a single method (.json(), .text(), …).
  • CORS is enforced by the browser—server must allow your origin/method/headers.
  • Don’t manually set Content-Type for FormData or most Blobs.

Cheat sheet

// GET
await fetch(url).then(r => r.json());

// POST JSON
await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data) });

// Text / Blob
const txt  = await (await fetch(url)).text();
const blob = await (await fetch(url)).blob();

// Headers
const res = await fetch(url);
res.headers.get('content-type');

FormData

FormData is a built-in JavaScript object used to collect and send form data — including files — to a server using fetch() or other network methods. It automatically encodes data as multipart/form-data, just like a regular form submission.

Creating FormData

// Create from an existing form
const formData = new FormData(document.querySelector('#myForm'));

// Or create manually
const fd = new FormData();
fd.append('name', 'John');
fd.append('surname', 'Smith');

Sending a Simple Form

<form id="userForm">
  <input type="text" name="name" value="John">
  <input type="text" name="surname" value="Smith">
  <input type="submit">
</form>

<script>
userForm.onsubmit = async (e) => {
  e.preventDefault();
  const res = await fetch('/api/users', {
    method: 'POST',
    body: new FormData(userForm)
  });
  const result = await res.json();
  alert(result.message);
};
</script>

FormData Methods

  • append(name, value) – Add a new field.
  • append(name, blob, fileName) – Add a file field (like <input type="file">).
  • set(name, value) – Replace existing field(s) with a new one.
  • delete(name) – Remove a field.
  • get(name) – Read a field’s value.
  • has(name) – Check if a field exists.

Note: append() adds another field with the same name; set() replaces all previous ones.

Example – Iterating through fields

const fd = new FormData();
fd.append('key1', 'value1');
fd.append('key2', 'value2');

for (const [name, value] of fd) {
  console.log(name, value);
}

Summary

  • new FormData(form) captures all fields of an HTML form.
  • Use append() and set() to add fields or files.

PART 6 · Storing Data in the Browser

Web storage APIs let you store data locally in the browser for later use.

Common options are localStorage (persistent) and sessionStorage (per-tab).

LocalStorage

localStorage is a built-in web storage object that lets you store key–value pairs in the browser permanently (until manually deleted). Data in localStorage:

  • Persists after page refresh or browser restart.
  • Is shared across all tabs and windows from the same origin (domain + protocol + port).
  • Is not sent to the server with requests (unlike cookies).
  • Can typically store 5–10 MB of data (depending on browser).

Basic API

  • setItem(key, value) – store a value.
  • getItem(key) – retrieve a value.
  • removeItem(key) – delete a specific key.
  • clear() – remove all keys.
  • key(index) – get the key name by index.
  • length – number of stored entries.

Example

localStorage.setItem('user', 'John');
console.log(localStorage.getItem('user')); // "John"

localStorage.removeItem('user');
localStorage.clear(); // remove everything

Object-like access (not recommended)

localStorage.test = 123;
console.log(localStorage.test); // 123
delete localStorage.test;

This works, but is not safe if the key overlaps with built-in names (e.g. length, toString). Always prefer getItem()/setItem().

Looping over keys

// by index
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, localStorage.getItem(key));
}

// or with Object.keys
for (const key of Object.keys(localStorage)) {
  console.log(key, localStorage.getItem(key));
}

Strings only

Both key and value must be strings. To store objects, use JSON.stringify() and JSON.parse().

// store object
const user = { name: 'John', age: 25 };
localStorage.setItem('user', JSON.stringify(user));

// read object
const stored = JSON.parse(localStorage.getItem('user'));
console.log(stored.name); // John

Storage Event (multi-tab sync)

When localStorage changes in one tab, other tabs (on the same origin) receive a storage event.

window.addEventListener('storage', e => {
  console.log('Changed:', e.key, e.oldValue, '→', e.newValue, 'from', e.url);
});

// in another tab
localStorage.setItem('theme', 'dark');

This enables simple communication between browser tabs.

Practical Uses

  • Save user preferences (e.g., theme, language).
  • Cache small JSON data or API responses for faster reloads.
  • Remember form input or app state between sessions.

Summary

FeaturelocalStorage
ScopeAll tabs/windows on the same origin
LifetimeUntil manually cleared
Size limit≈ 5 MB per origin
Data typeString (convert objects using JSON)
Eventstorage (syncs across tabs)
💡 Tip: Use localStorage for small, persistent data. Use sessionStorage for temporary, tab-specific data.