β‘ JavaScript: The Language
JavaScript is the programming language of the web. It runs in every browser, on every server with Node.js, and even on mobile apps with React Native. One language β web, server, mobile, desktop. This module covers the core language: the syntax and concepts you'll reuse everywhere.
π― By the end of this module, you will:
- Declare variables with
letandconstand understand types - Write conditionals, loops, and control flow
- Create functions (declarations, expressions, arrow functions)
- Use callbacks β the pattern behind async JavaScript
- Work with objects and understand
this - Use spread and rest operators (
...) for flexible data handling - Write clean strings with template literals
π¦ Variables & Types
const β
Recommended
Can't be reassigned. Use by default.
let π When needed
Block-scoped. Use when the value changes.
var β οΈ Avoid
Function-scoped, hoisted. Legacy only.
π What is Hoisting?
JavaScript moves var and function declarations to the top of their scope before execution. This means you can use them before they appear in code. With var, the value is undefined until assigned. const/let throw an error if used before declaration (the "temporal dead zone").
// Variable declarations
const PI = 3.14159; // can't be reassigned
let count = 0; // can be reassigned
count = 1; // β
OK
// PI = 3; // β TypeError
// Dynamic typing: same variable can hold different types
let x = 42;
x = "hello"; // β
No error (dynamic typing)
// JavaScript Types (7 primitives + object)
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" β quirk!
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects)
console.log(typeof function(){}); // "function"π·οΈ JavaScript Types (typeof results)
Quirk: typeof null === "object" is a famous JavaScript bug from 1995 that was never fixed for backwards compatibility. Always check with === null.
π Conditionals
π Logical Operators at a Glance
&&
||
!
? :
// if / else if / else
const age = 20;
if (age >= 18) {
console.log("Adult");
} else if (age >= 13) {
console.log("Teenager");
} else {
console.log("Child");
}
// Ternary operator
const status = age >= 18 ? "adult" : "minor";
// Logical operators
const hasLicense = true;
if (age >= 18 && hasLicense) {
console.log("Can drive");
}
if (age < 18 || !hasLicense) {
console.log("Cannot drive");
}
// Comparison operators
console.log(5 > 3); // true
console.log(5 >= 5); // true
console.log(5 !== 3); // true (strict inequality)== vs =====
Loose (type coercion)
"5" == 5 β true===
Strict (prefer!)
"5" === 5 β false// β οΈ == vs === (ALWAYS use ===)
console.log(1 == "1"); // true (type coercion!)
console.log(1 === "1"); // false (strict equality β
)
console.log("" == 0); // true (loose - avoid!)
console.log("" === 0); // false (strict - prefer!)Rule: Always use === (strict equality). The == operator does type coercion which leads to surprising results like "" == 0 being true.
π switch Statement
Use switch when comparing one value against many cases. Always include break (or return) to prevent fall-through, and a default for unmatched values.
const role = "admin";
switch (role) {
case "admin":
console.log("Full access");
break;
case "editor":
console.log("Can edit content");
break;
case "viewer":
console.log("Read only");
break;
default:
console.log("Unknown role");
}
// β οΈ Without break β fall-through!
switch ("B") {
case "A":
case "B":
case "C":
console.log("Letter A, B, or C"); // groups cases
break;
default:
console.log("Other letter");
}β β Truthy & Falsy Values
JavaScript automatically converts values to true or false in boolean contexts (if, &&, ||, !). Memorize the 6 falsy values β everything else is truthy.
false Β· 0 Β· "" Β· null Β· undefined Β· NaN
"hello" Β· 42 Β· [] Β· {} Β· "0" Β· "false"
// Falsy values (all 6)
if (!false) console.log("false is falsy");
if (!0) console.log("0 is falsy");
if (!"") console.log("empty string is falsy");
if (!null) console.log("null is falsy");
if (!undefined) console.log("undefined is falsy");
if (!NaN) console.log("NaN is falsy");
// β οΈ Surprising truthy values
if ("0") console.log("string '0' is truthy!"); // β
if ([]) console.log("empty array is truthy!"); // β
if ({}) console.log("empty object is truthy!"); // β
// Practical: guard against empty/missing values
const username = "";
if (username) {
console.log(`Welcome, ${username}`);
} else {
console.log("Please enter a name"); // β this runs
}β Nullish Coalescing (??) & Optional Chaining (?.)
Two modern operators for handling null/undefined safely β without accidentally catching 0, "", or false.
??
null/undefined?.
// ?? β Nullish coalescing: fallback for null/undefined ONLY
const port = 0;
console.log(port || 3000); // 3000 β WRONG! 0 is falsy
console.log(port ?? 3000); // 0 β CORRECT! 0 is not null/undefined
const name = "";
console.log(name || "Guest"); // "Guest" β treats "" as falsy
console.log(name ?? "Guest"); // "" β only null/undefined trigger fallback
// ?. β Optional chaining: safe nested access
const user = { address: { city: "Casablanca" } };
console.log(user.address.city); // "Casablanca"
console.log(user.phone?.number); // undefined (no error!)
console.log(user.getAge?.()); // undefined (method doesn't exist)
// Without ?. this would throw:
// console.log(user.phone.number); // β TypeError!
// Combine ?? and ?.
const theme = user.settings?.theme ?? "light";
console.log(theme); // "light" (safe fallback)π Loops
π Loop Types at a Glance
for
for (i=0; i<n; i++)
for...of
BEST FOR ARRAYS
for (v of array)
for...in
FOR OBJECTS
for (key in obj)
while
while (cond) { }
do...while
do { } while (cond)
// Classic for loop
for (let i = 0; i < 5; i++) {
console.log(i); // 0, 1, 2, 3, 4
}
// for...of β BEST for arrays β
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// for...in β for object keys
const user = { name: "Mehdi", role: "teacher" };
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// while
let n = 0;
while (n < 3) {
console.log(n++); // 0, 1, 2
}
// do...while (runs at least once)
let x = 10;
do {
console.log(x); // 10 (runs once even though x >= 5)
} while (x < 5);Best practice: Use for...of for arrays and iterables. Use for...in for object properties. Use classic for when you need the index.
π§ Functions
Functions are the building blocks of JavaScript. There are three ways to define them:
Declaration
function greet() { }
Hoisted β
Β· Has this Β· Has arguments Β· Can use new
Expression
const greet = function() { }
Not hoisted Β· Has this Β· Has arguments Β· Can use new
Arrow
const greet = () => { }
Not hoisted Β· Lexical this Β· No arguments Β· β No new
// Function declaration (hoisted β can be called before definition)
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
// Function expression
const multiply = function(a, b) {
return a * b;
};
// Any `function` can be called with `new` to create an object!
function Car(brand, year) {
this.brand = brand;
this.year = year;
}
const myCar = new Car("Toyota", 2024);
console.log(myCar); // Car { brand: "Toyota", year: 2024 }
console.log(myCar.brand); // "Toyota"
// Even a simple function works with `new`:
function empty() {}
const obj = new empty();
console.log(obj); // empty {} β a new object!
console.log(typeof obj); // "object"
// β οΈ Arrow functions CANNOT be used with `new`
// const f = () => {};
// new f(); // β TypeError: f is not a constructorfunction + new : Any function defined with the function keyword can be called with new functionName(). This creates a new empty object, sets this to it, runs the function body, and returns the object. This is how JavaScript created objects before classes existed. Arrow functions cannot be used with new β they are not constructors.
β‘ Parameter Flexibility
JavaScript doesn't enforce argument counts: missing parameters are undefined, extra arguments are ignored unless you read them.
// β οΈ JavaScript is flexible with argument count
function demo(a, b) {
console.log(a, b);
}
demo(1); // 1, undefined (missing args β undefined)
demo(1, 2, 3); // 1, 2 (extra args silently ignored)Default Parameters
// Default parameters
function greet(name = "World") {
return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, World!"
console.log(greet("Mehdi")); // "Hello, Mehdi!"
// Defaults are evaluated at call time (not definition time!)
function getId() { return Math.floor(Math.random() * 1000); }
function makeUser(id = getId(), role = 'reader') {
return { id, role };
}
console.log(makeUser()); // { id: 742, role: 'reader' }
console.log(makeUser()); // { id: 318, role: 'reader' } β different!The arguments Object
Traditional (non-arrow) functions expose arguments, which contains all values passed. Prefer rest parameters for clarity.
// The arguments object (array-like, NOT a real array)
function sumUsingArguments() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sumUsingArguments(1, 2, 3)); // 6Rest Parameters (...args)
Use rest parameters to collect any additional arguments. Rules: only one rest parameter and it must be the last in the parameter list.
// Rest parameters (...args collects remaining arguments)
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// Mixing named parameters with rest
function log(label, ...values) {
console.log(`[${label}]`, values);
}
log('nums', 10, 20, 30); // [nums] [10, 20, 30]
// β Invalid: rest must be the last parameter
// function badRest(...a, b) {} // SyntaxError!No True Overloads in JavaScript
Declaring the same function name twice overwrites the previous definition. Simulate overloads with optional/default/rest parameters and runtime checks.
// Simulate overloads with runtime checks
function format(value, options = {}) {
if (typeof value === 'number') return value.toFixed(options.decimals ?? 2);
if (typeof value === 'boolean') return value ? 'yes' : 'no';
return String(value);
}
console.log(format(3.14159)); // "3.14"
console.log(format(true)); // "yes"
console.log(format(3.14, {decimals:1})); // "3.1"Function Names (for debugging)
// Function names appear in stack traces when debugging
function hello() {}
const greet2 = function () {};
const wave = () => {};
console.log(hello.name); // "hello"
console.log(greet2.name); // "greet2" (modern JS infers from variable)
console.log(wave.name); // "wave" (arrow functions too!)π Closures
A closure is a function that remembers the variables from the scope where it was created β even after that scope has finished executing. This is one of JavaScript's most powerful features.
// A closure "remembers" its birth scope
function createCounter() {
let count = 0; // private variable
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is NOT accessible from outside!
// Practical: function factory
function createMultiplier(factor) {
return (number) => number * factor; // factor is "closed over"
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15Key insight: Closures are the foundation of many patterns: private variables, function factories, callbacks, and module patterns. You've already seen closures in the Counter encapsulation example in the Objects section!
π IIFE (Immediately Invoked Function Expression)
An IIFE is a function that runs immediately after being defined. It creates a private scope to avoid polluting the global namespace.
// IIFE β runs immediately, creates a private scope
(function() {
const secret = "hidden from outside";
console.log(secret); // "hidden from outside"
})();
// console.log(secret); // β ReferenceError!
// IIFE with arrow function
(() => {
const config = { debug: true, version: "1.0" };
console.log(config.version);
})();
// IIFE returning a value (module pattern)
const calculator = (function() {
let history = [];
return {
add(a, b) {
const result = a + b;
history.push(`${a}+${b}=${result}`);
return result;
},
getHistory() { return [...history]; }
};
})();
console.log(calculator.add(2, 3)); // 5
console.log(calculator.getHistory()); // ["2+3=5"]
// calculator.history is undefined β private!Summary: Use traditional functions when you need your own this or arguments, or when defining constructors/methods. Use rest/default parameters for flexibility; avoid relying on implicit arguments.
β‘οΈ Arrow Functions
Arrow functions are a shorter syntax for writing functions, introduced in ES6. They also behave differently with this.
(parameters)
=>
expression | { statements }
β¨ Syntax Variations
() => Date.now()
x => x * 2
(a, b) => a + b
(x) => { return x; }
this
arguments
// Syntax variations
const double = (x) => x * 2; // one param: parens optional
const add = (a, b) => a + b; // multiple params: parens required
const greet = () => "Hello!"; // no params: empty parens
const process = (data) => { // block body: explicit return
const result = data * 2;
return result;
};
// Implicit return (no braces = auto-return)
const names = ["Mehdi", "Ada", "Alan"];
const lengths = names.map(name => name.length); // [5, 3, 4]
// β οΈ Returning an object literal? Wrap in parens:
const makeUser = (name) => ({ name, role: "student" });Always anonymous: Arrow functions are always anonymous β they have no name of their own. However, modern JavaScript infers the name from the variable: const greet = () => {}; console.log(greet.name); // "greet"
Practical Examples
// Array methods love arrows
const numbers = [1, 2, 3, 4, 5];
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15
// Event listeners
// document.querySelector('button').addEventListener('click', (evt) => {
// console.log('Clicked at', evt.clientX, evt.clientY);
// });
// Timers
setTimeout(() => console.log('Hello after 1s'), 1000);
// Simple math helpers
const square = x => x * x;
const makePoint = (x, y) => ({ x, y });Choose wisely: Use arrows for concise callbacks and when lexical this helps; use traditional functions for methods/constructors or when you need your own this/arguments.
π Callbacks
A callback is a function passed as an argument to another function. This pattern is everywhere in JavaScript β event handlers, array methods, and async operations all use callbacks.
How Callbacks Work
Receives a callback parameter
Processes data, waits for eventsβ¦
Invokes the callback with results
function compute(a, b) {
console.log(a + b); // Fixed!
}
function compute(a, b, cb) {
cb(a + b); // You decide!
}
// Basic callback pattern
function compute(a, b, callback) {
const result = a + b;
callback(result); // "call back" with the result
}
compute(2, 3, (sum) => {
console.log(`The sum is ${sum}`); // "The sum is 5"
});
// You already use callbacks everywhere:
[1, 2, 3].forEach(n => console.log(n)); // Array method
setTimeout(() => console.log("done"), 1000); // TimerWhy Callbacks? Decoupling Behavior from Logic
Problem β Hardcoded Behavior
function randomNumber() {
return Math.floor(Math.random() * 5) + 1;
}
function devinette(nbr) {
const aleatoire = randomNumber();
if (aleatoire === nbr) {
console.log("bravo"); // β Fixed behavior!
} else {
console.log("echec"); // β Can't change this
}
}
devinette(3);Limitation: The author of devinette decided the success/failure behavior. The caller cannot log differently, update UI, count attempts, or do anything else.
Solution β Pass Callbacks
Let the function focus on logic (checking the guess) and let the caller choose what to do on success and on failure.
function randomNumber() {
return Math.floor(Math.random() * 5) + 1;
}
function devinette(nbr, onSuccess, onFailure) {
const secret = randomNumber();
if (secret === nbr) {
onSuccess(secret, nbr);
} else {
onFailure(secret, nbr);
}
}
// Caller decides behaviors!
devinette(3,
(secret, guess) => console.log("π bravo β secret:", secret),
(secret, guess) => console.log("β echec β secret:", secret)
);Benefit: The game logic doesn't know about printing, UI, or storage. The caller controls side-effects, which makes the function reusable.
Variant β Single Callback with Result Object
function devinette(nbr, done) {
const secret = Math.floor(Math.random() * 5) + 1;
const success = secret === nbr;
done({ success, secret, guess: nbr });
}
devinette(4, (r) => {
if (r.success) {
console.log("bravo β", r);
} else {
console.log("echec β", r);
}
});Callback Hell: Nesting many callbacks creates a "pyramid of doom". We'll solve this with Promises and async/await in Module 2.
ποΈ Objects
Objects are collections of key-value pairs. They're the most important data structure in JavaScript β almost everything is an object.
ποΈ Ways to Create Objects
const user = {
name: "Mehdi",
hello() { console.log(this.name); }
};
function Person(name) {
this.name = name;
}
const p = new Person("Sara");
class Person {
constructor(name) { this.name = name; }
hello() { console.log(this.name); }
}
π Object Operations
obj.prop = value // add/set
delete obj.prop // remove
"prop" in obj // check
// Object literal (most common way)
const user = {
name: "Mehdi",
age: 30,
isTeacher: true,
greet() {
return `Hi, I'm ${this.name}`;
}
};
console.log(user.name); // "Mehdi" (dot notation)
console.log(user["age"]); // 30 (bracket notation)
console.log(user.greet()); // "Hi, I'm Mehdi"
// Dynamic properties
user.email = "mehdi@example.com"; // add property
delete user.age; // remove property
console.log("name" in user); // true (check existence)
// Object.keys / values / entries
console.log(Object.keys(user)); // ["name", "isTeacher", "greet", "email"]
console.log(Object.values(user)); // ["Mehdi", true, Ζ, "mehdi@..."]// Constructor function β use with `new` (see Functions section above)
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
const s1 = new Student("Ada", "A");
// ES2015+ Class syntax (also uses `new`)
class Teacher {
constructor(name, subject) {
this.name = name;
this.subject = subject;
}
introduce() {
return `${this.name} teaches ${this.subject}`;
}
}
const t1 = new Teacher("Mehdi", "JavaScript");π More Ways to Create Objects
// Object.create() β create with a specific prototype
const animal = {
speak() { console.log(`${this.name} makes a sound`); }
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak(); // "Rex makes a sound"
console.log(Object.getPrototypeOf(dog) === animal); // true
// Factory function β returns a new object (no `new` needed)
function createUser(name, role) {
return {
name,
role,
greet() { return `Hi, I'm ${this.name} (${this.role})`; }
};
}
const u1 = createUser("Sara", "admin");
const u2 = createUser("Ali", "viewer");
console.log(u1.greet()); // "Hi, I'm Sara (admin)"π οΈ Essential Object Methods
Built-in methods for copying, freezing, and inspecting objects:
Object.assign()
Object.freeze()
Object.entries()
Object.create()
// Object.assign() β copy / merge (shallow)
const defaults = { theme: "light", lang: "en", debug: false };
const userPrefs = { theme: "dark", lang: "fr" };
const config = Object.assign({}, defaults, userPrefs);
console.log(config); // { theme: "dark", lang: "fr", debug: false }
// Note: spread is often preferred: { ...defaults, ...userPrefs }
// Object.freeze() β make object immutable
const API = Object.freeze({
BASE_URL: "https://api.example.com",
VERSION: "v2"
});
API.BASE_URL = "hacked"; // β silently fails (or throws in strict mode)
console.log(API.BASE_URL); // "https://api.example.com" β unchanged!
// Object.entries() β iterate key-value pairs
const scores = { math: 95, english: 87, science: 92 };
for (const [subject, score] of Object.entries(scores)) {
console.log(`${subject}: ${score}`);
}
// math: 95, english: 87, science: 92
// Dynamic method addition
const user = { name: "Mehdi" };
user.rename = function(newName) {
this.name = newName;
};
user.rename("Sara");
console.log(user.name); // "Sara"Shallow freeze: Object.freeze() only freezes the top level. Nested objects can still be modified. Use libraries like Immer for deep immutability.
π Encapsulation: Public vs Private (closures)
We can mimic private data by using a local let variable inside the constructor (captured by closure) and expose only the methods we want via this. Properties on this are public.
function Counter(start) {
// private (not accessible from the instance)
let count = (typeof start === 'number') ? start : 0;
// public methods (privileged: they can see 'count')
this.get = function () { return count; };
this.inc = function () { count++; };
this.reset = function () { count = 0; };
}
const c = new Counter(5);
console.log(c.count); // undefined (private!)
console.log(c.get()); // 5
c.inc();
console.log(c.get()); // 6
c.reset();
console.log(c.get()); // 0π― The this Keyword
this refers to the object that is executing the current function. But its value changes depending on how the function is called.
obj.method() β this = obj
func() β this = undefined/window
Always uses outer this
Perfect for callbacks!
arguments β
arguments β
// Regular function: `this` depends on the caller
const obj = {
name: "Mehdi",
greet() {
console.log(`Hello, ${this.name}`);
}
};
obj.greet(); // "Hello, Mehdi" (this = obj)
// β οΈ Lost context in callbacks (setTimeout demo)
const obj2 = {
name: "Ada",
greetLater() {
// Regular function: `this` is lost in setTimeout
setTimeout(function() {
console.log(this.name); // undefined! (this = window/global)
}, 100);
},
greetLaterFixed() {
// Arrow function: `this` inherited from greetLaterFixed
setTimeout(() => {
console.log(this.name); // "Ada" β
}, 100);
}
};// The arguments object (regular functions only)
function demo() {
console.log(arguments); // [1, 2, 3] (array-like)
const arr = Array.from(arguments); // convert to real array
console.log(arr.join(", ")); // "1, 2, 3"
}
demo(1, 2, 3);
// Arrow functions: use rest instead
const demo2 = (...args) => {
console.log(args); // [1, 2, 3] (real array)
};
demo2(1, 2, 3);ποΈ call(), apply(), and bind()
These three methods let you manually set the value of this when calling a function β useful when a function's context is lost or needs to be reused.
.call(thisArg, a, b)
this + args one by one.apply(thisArg, [a, b])
this + args as array.bind(thisArg)
thisconst person = {
name: "Mehdi",
greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
};
const other = { name: "Sara" };
// call β invoke with different `this`, args listed
console.log(person.greet.call(other, "Hello", "!"));
// "Hello, Sara!"
// apply β same, but args as array
console.log(person.greet.apply(other, ["Hi", "?"]));
// "Hi, Sara?"
// bind β returns NEW function with fixed `this`
const saraGreet = person.greet.bind(other);
console.log(saraGreet("Hey", ".")); // "Hey, Sara."
// Practical: fixing lost context in callbacks
const button = {
label: "Submit",
click() { console.log(`Clicked: ${this.label}`); }
};
// setTimeout(button.click, 100); // β this = undefined
setTimeout(button.click.bind(button), 100); // β
this = buttonWhen to use which? call/apply invoke immediately (use apply when args are already in an array). bind creates a reusable function with a fixed this β perfect for event handlers and callbacks.
Rule of thumb: Use arrow functions for callbacks (to keep this from the surrounding scope). Use regular functions for object methods (where you want this to be the object).
π Rest & Spread Operators (...)
The ... syntax serves two opposite purposes depending on context. Understanding the difference is crucial:
function sum(...nums) { }
Math.max(...arr)
π Quick Reference
fn(a, ...rest)
[a, ...rest] = arr
[...arr1, ...arr2]
{...obj, newProp}
REST: Collecting Multiple β One
// REST in function parameters: collect arguments into array
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// REST with named parameters (rest must be last!)
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}!`;
}
console.log(greet('Hello', 'Ali', 'Sara', 'Omar'));
// "Hello Ali, Sara, Omar!"
// REST in array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [3, 4, 5]
// REST in object destructuring
const user = { name: 'Mehdi', age: 32, city: 'Casa', role: 'dev' };
const { name, ...otherProps } = user;
console.log(name); // "Mehdi"
console.log(otherProps); // { age: 32, city: 'Casa', role: 'dev' }SPREAD: Expanding One β Multiple
// SPREAD in arrays: expand array into individual elements
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
const clone = [...arr1]; // [1, 2, 3] (shallow copy)
// SPREAD in function calls
const nums = [1, 5, 3, 9, 2];
console.log(Math.max(...nums)); // 9
// SPREAD in objects: merge & override
const userBase = { name: "Mehdi", age: 32 };
const location = { city: "Casablanca", country: "Morocco" };
const profile = { ...userBase, ...location };
// { name: "Mehdi", age: 32, city: "Casablanca", country: "Morocco" }
// Override properties (later values win)
const updated = { ...userBase, age: 33, role: "admin" };
// { name: "Mehdi", age: 33, role: "admin" }Putting It Together: REST + SPREAD
// Use both in same function!
function mergeAndLog(label, ...arrays) { // REST: collect arrays
const merged = [].concat(...arrays); // SPREAD: expand each
console.log(`${label}:`, merged);
return merged;
}
mergeAndLog('Numbers', [1, 2], [3, 4], [5, 6]);
// "Numbers: [1, 2, 3, 4, 5, 6]"
// Practical: immutable state updates
const state = {
user: { name: 'Ali', age: 25 },
settings: { theme: 'dark', lang: 'en' }
};
const newState = {
...state, // SPREAD: copy all
user: {
...state.user, // SPREAD: copy user
age: 26 // Override age
}
};
console.log(state.user.age); // 25 (original unchanged)
console.log(newState.user.age); // 26Common Mistakes:
// β WRONG: Rest must be last parameter
// function bad(...first, last) { } // SyntaxError!
// β
CORRECT:
function good(first, ...rest) { }
// β WRONG: Can't spread into nothing
// const x = ...arr; // SyntaxError!
// β
CORRECT: Spread inside array/object/call
const x = [...arr];
const y = Math.max(...arr);β¨ Template Literals
Template literals use backticks (\`) and provide a cleaner, more readable way to work with strings compared to concatenation.
Why Template Literals? The Problem with Concatenation
const user = { name: 'Mehdi', email: 'mehdi@example.com' };
const orderCount = 5;
const total = 249.99;
// β CONCATENATION: Hard to read, easy to make mistakes
const msg1 = 'Hello ' + user.name + ',\n' +
'You have ' + orderCount + ' orders.\n' +
'Total: $' + total.toFixed(2);
// β
TEMPLATE LITERAL: Clean, readable, maintainable
const msg2 = `Hello ${user.name},
You have ${orderCount} orders.
Total: $${total.toFixed(2)}
Email: ${user.email}`;
console.log(msg2);Basic Features
const name = "Mehdi";
const age = 32;
// Expression interpolation
const greeting = `Hello, ${name}! You are ${age} years old.`;
// Any expression works inside ${}
const price = 100;
const tax = 0.2;
const total = `Total: ${price * (1 + tax)} MAD`;
console.log(total); // Total: 120 MAD
// Function calls
const loud = (text) => text.toUpperCase();
console.log(`Message: ${loud('hello')}`); // Message: HELLO
// Conditional expressions
const status = age >= 18 ? 'adult' : 'minor';
const info = `${name} is an ${status}`; // "Mehdi is an adult"Practical Use Cases
// Use Case 1: HTML Generation
function createUserCard(user) {
return `
<div class="user-card">
<h3>${user.name}</h3>
<p>Email: ${user.email}</p>
<span class="${user.active ? 'active' : 'inactive'}">
${user.active ? 'β Active' : 'β Inactive'}
</span>
</div>
`;
}
// Use Case 2: API URLs
const userId = 123;
const apiUrl = `https://api.example.com/users/${userId}/posts?limit=10`;
// Use Case 3: Log messages with context
function logAction(action, user) {
console.log(`[${new Date().toISOString()}] ${user.name}: ${action}`);
}
logAction('login', { name: 'Ali' });When to use each: Use template literals for multi-line strings, complex interpolation, HTML/SQL generation. Use simple concatenation for 'Hello' + name. General rule: if you need more than 2 values or multiple lines β use templates.
π Summary
π¦ Variables
Use const by default, let when needed. Avoid var.
π Control Flow
if/else, ternary, === strict equality. 5 loop types.
π§ Functions
Declaration, expression, arrow. Default params, rest ...args.
β‘οΈ Arrows
Shorter syntax, lexical this, no arguments. Perfect for callbacks.
π Callbacks
Functions passed as arguments. The foundation of async patterns.
ποΈ Objects
Key-value pairs. Literals, constructors, classes. this is context-dependent.
π Spread & Rest
Rest gathers into arrays, spread expands into elements. Same ... syntax.
β¨ Templates
Backtick strings with ${} interpolation. Multi-line, readable, powerful.