Module 1

JavaScript Language Core

Variables β€’ Functions β€’ Objects β€’ this

⚑ 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 let and const and 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)

number string boolean undefined object function bigint symbol

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

&&
AND
Both must be true
||
OR
At least one true
!
NOT
Inverts value
? :
Ternary
Inline if/else
// 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)
⚠️ Equality: == 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.

❌ Falsy (6 values)
false Β· 0 Β· "" Β· null Β· undefined Β· NaN
βœ… Truthy (everything else)
"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.

??
Nullish Coalescing
Default only for null/undefined
?.
Optional Chaining
Access nested props safely
// ?? β€” 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
Classic counter loop
for (i=0; i<n; i++)
πŸ“¦ for...of BEST FOR ARRAYS
Iterate values
for (v of array)
πŸ”‘ for...in FOR OBJECTS
Iterate keys
for (key in obj)
⏳ while
Check first, then run
while (cond) { }
πŸ” do...while
Run first, then check
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);
πŸ’‘ for...of for arrays (values)  β€’  for...in for objects (keys)

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 constructor
πŸ”‘ function + 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)); // 6

Rest 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));  // 15

Key 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

Zero Parameters
() => Date.now()
One Parameter
x => x * 2
Multiple Parameters
(a, b) => a + b
Block Body
(x) => { return x; }
βœ“ Shorter syntax
βœ“ Lexical this
βœ— No own arguments
βœ— Cannot be constructors
// 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" });
πŸ’‘ Return Rules
Expression body (no braces)
x => x * 2 β†’ implicit return
Block body (with braces)
x => { return x * 2; }
Return object literal
(x,y) => ({ x, y })

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

1
Your Function

Receives a callback parameter

2
Does Work

Processes data, waits for events…

3
Calls Back

Invokes the callback with results

πŸ’‘ Key Concept
A callback is a function you pass as an argument. The receiving function calls it later with results. This lets YOU control what happens with the data!
❌ Hardcoded Behavior
function compute(a, b) {
  console.log(a + b); // Fixed!
}
Can't customize output
βœ… With Callback
function compute(a, b, cb) {
  cb(a + b); // You decide!
}
Caller controls behavior
// 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);     // Timer

Why 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

{ } Object Literal MOST COMMON
const user = {
  name: "Mehdi",
  hello() { console.log(this.name); }
};
πŸ”§ Constructor Function
function Person(name) {
  this.name = name;
}
const p = new Person("Sara");
πŸ“¦ Class (ES2015+) MODERN
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()
Copy / merge objects
Object.freeze()
Make immutable
Object.entries()
Get [key, value] pairs
Object.create()
Create with prototype
// 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.

πŸ“œ ⚑ Regular Function
⚑ Dynamic this
Depends on how it's called
obj.method() β†’ this = obj func() β†’ this = undefined/window
➑️ πŸ”’ Arrow Function
πŸ”’ Lexical this
Inherits from where defined
Always uses outer this Perfect for callbacks!
Regular Function
arguments βœ“
Arrow Function
arguments βœ—
Use ...rest instead
// 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)
Call with this + args one by one
.apply(thisArg, [a, b])
Call with this + args as array
.bind(thisArg)
Returns a new function with fixed this
const 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 = button

When 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:

πŸ“₯ REST (Gather)
1 2 3
↓
[1, 2, 3]
function sum(...nums) { }
Multiple items β†’ One array
πŸ“€ SPREAD (Scatter)
[1, 2, 3]
↓
1 2 3
Math.max(...arr)
One array β†’ Multiple items
πŸ“ Quick Reference
REST - Parameters
fn(a, ...rest)
REST - Destructure
[a, ...rest] = arr
SPREAD - Arrays
[...arr1, ...arr2]
SPREAD - Objects
{...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); // 26

Common 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.

❌ Concatenation (Old)
'Hello ' + name + '!'
βœ… Template Literal (Modern)
\`Hello ${name}!\`
πŸ“
Multi-line Strings
No \n needed
⚑
Expression Interpolation
${expression}
🏷️
Tagged Templates
tag\`string\`

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.

2
Next Module 2 / 7

Module 2: Data Structures & Async

Destructuring, JSON, arrays, array methods, error handling, Promises, and async/await.

destructuring JSON arrays map / filter / reduce Promises async / await
β†’