JavaScript Fundamentals Review

Week 4: Web Fundamentals - Thursday Morning Session

Revisiting JavaScript Core Concepts

Welcome to our JavaScript fundamentals review! As Python developers transitioning to full-stack development, understanding JavaScript is crucial for creating interactive web applications. This session will revisit key JavaScript concepts to ensure you have a solid foundation before we dive deeper into DOM manipulation and event handling.

This lecture belongs to Week 4: Web Fundamentals and is part of our Python Full Stack Developer course. You'll find this content in the folder structure: /04week/04week_4day_e.html

JavaScript is the language of the web browser, enabling dynamic interactions that transform static HTML documents into interactive applications. While there are similarities between Python and JavaScript (both are high-level, interpreted languages), there are also important differences in syntax, behavior, and philosophy that we'll highlight throughout this review.

Brief History and Evolution of JavaScript

Before diving into the syntax, let's understand where JavaScript comes from and how it has evolved:

River Metaphor: Think of JavaScript's evolution like a river that started as a small stream (simple, limited features) but has grown through tributaries and rainfall (community contributions and standards committees) into a powerful flowing river with many branches (frameworks, libraries, and dialects) that all share the same fundamental properties.

JavaScript Today

Modern JavaScript (post-ES6) has addressed many of the language's early criticisms and added powerful features:

  • Arrow functions, promises, and async/await for better asynchronous code
  • Classes and modules for improved code organization
  • Template literals, destructuring, and spread syntax for cleaner syntax
  • Block-scoped variables (let, const) for safer scoping
  • Expanded in server environments (Node.js) and desktop applications (Electron)

Variables and Data Types

Like Python, JavaScript is dynamically typed, but with some important differences in how variables and types work.

Variable Declaration

JavaScript offers three ways to declare variables, each with different behaviors:

// var - function-scoped, older style (pre-ES6)
var message = "Hello, world!"; 
var count = 42;

// let - block-scoped, preferred for variables that will change
let counter = 0;
counter += 1; // counter is now 1

// const - block-scoped, for values that won't be reassigned
const PI = 3.14159;
const MAX_USERS = 100;

// Error - can't reassign a const
// PI = 3.14; // TypeError: Assignment to constant variable

// But objects and arrays declared with const can be modified
const user = { name: "Alice" };
user.name = "Bob"; // This works! Only the reference is constant
user.age = 30;     // Adding properties is fine

const numbers = [1, 2, 3];
numbers.push(4);   // This works! Array content can change

Key Differences from Python:

  • JavaScript requires declaration keywords (var, let, const)
  • Variables have block or function scope, not just function scope like Python
  • No built-in concept like Python's tuples for immutable sequences

Container Metaphor: Think of var as a balloon that floats up to its containing function, let as a box that stays exactly where you put it, and const as a locked box where you can rearrange the contents but can't replace the entire box.

Primitive Data Types

JavaScript has seven primitive (built-in) data types:

// 1. String - for text
let name = "Alice";
let greeting = 'Hello';
let template = `Hello, ${name}!`; // Template literal (ES6)

// 2. Number - both integers and floating-point
let count = 42;
let price = 9.99;
let scientific = 3e8; // 3 x 10^8 (300,000,000)

// 3. Boolean - true or false
let isActive = true;
let hasPermission = false;

// 4. null - intentional absence of any value
let user = null; // user is explicitly empty

// 5. undefined - unintentionally missing value
let pet; // pet is declared but not defined
console.log(pet); // undefined

// 6. Symbol - unique and immutable (ES6)
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false, symbols are always unique

// 7. BigInt - for integers larger than Number can represent (ES2020)
const bigNumber = 9007199254740991n; // n suffix defines a BigInt
const result = bigNumber + 1n;

Python Comparison: Unlike Python, JavaScript has a single Number type for both integers and floats. Also, empty values in JavaScript are more complex with both null (explicit emptiness) and undefined (absence of definition).

Objects and Reference Types

Beyond primitives, JavaScript has reference types (sometimes called "objects" broadly):

// Objects - collections of key-value pairs (like Python dictionaries)
const person = {
    firstName: "Alice",
    lastName: "Johnson",
    age: 28,
    isStudent: false,
    greet: function() {
        return `Hello, my name is ${this.firstName}`;
    }
};

// Accessing object properties
console.log(person.firstName); // Dot notation
console.log(person["lastName"]); // Bracket notation
console.log(person.greet()); // Method call

// Arrays - ordered collections (similar to Python lists)
const colors = ["red", "green", "blue"];
const mixed = [1, "two", true, null, {key: "value"}]; // Mixed types

// Accessing array elements (zero-based indexing)
console.log(colors[0]); // "red"
colors.push("yellow"); // Add to the end
colors.unshift("purple"); // Add to the beginning
const lastColor = colors.pop(); // Remove from the end

// Functions - callable objects
function add(a, b) {
    return a + b;
}

// Functions as values
const multiply = function(a, b) {
    return a * b;
};

// Arrow functions (ES6)
const divide = (a, b) => a / b; // Implicit return
const square = a => a * a; // Single parameter, no parentheses needed

Key Difference from Python: JavaScript functions are first-class objects that can be assigned to variables, passed as arguments, and returned from other functions (similar to Python, but with different syntax).

Type Coercion and Checking

JavaScript will automatically convert types in certain operations, which can lead to unexpected results:

// Type Coercion Examples
console.log("5" + 3);     // "53" (string concatenation)
console.log("5" - 3);     // 2 (numeric subtraction)
console.log("5" * "3");   // 15 (numeric multiplication)
console.log(true + 1);    // 2 (true converts to 1)
console.log(false + 1);   // 1 (false converts to 0)
console.log(null + 1);    // 1 (null converts to 0)
console.log(undefined + 1); // NaN (undefined converts to NaN)

// Equality operators
console.log("5" == 5);    // true (loose equality, with coercion)
console.log("5" === 5);   // false (strict equality, no coercion)
console.log(0 == false);  // true (coercion)
console.log(0 === false); // false (no coercion)

// Type checking
console.log(typeof "hello");      // "string"
console.log(typeof 42);           // "number"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof null);         // "object" (this is a known bug in JS)
console.log(typeof {});           // "object"
console.log(typeof []);           // "object" (arrays are objects)
console.log(typeof function() {}); // "function"

// Better array checking
console.log(Array.isArray([]));   // true
console.log(Array.isArray({}));   // false

Traffic Light Metaphor: If Python's type system is like a traffic light with clear signals (red, yellow, green), JavaScript's type coercion is like a blinking yellow light where rules exist but you need extra caution. The === operator is like installing a guard rail to prevent unexpected behavior.

Python Developer Tip: Always prefer the strict equality operators (=== and !==) in JavaScript to avoid unexpected type coercion, similar to how you wouldn't rely on Python's implicit type conversion in comparisons.

Operators and Expressions

JavaScript operators are similar to those in Python, with some important differences:

Arithmetic Operators

// Basic arithmetic
let a = 10;
let b = 3;

console.log(a + b);  // 13 (addition)
console.log(a - b);  // 7 (subtraction)
console.log(a * b);  // 30 (multiplication)
console.log(a / b);  // 3.3333... (division - always returns floating point)
console.log(a % b);  // 1 (modulus - remainder after division)
console.log(a ** b); // 1000 (exponentiation - ES2016)

// Increment and decrement
let c = 5;
c++;        // Post-increment (c is now 6)
++c;        // Pre-increment (c is now 7)
console.log(c++); // 7 (returns current value, then increments)
console.log(++c); // 9 (increments first, then returns new value)

// Assignment operators
let d = 10;
d += 5;      // d = d + 5 (d is now 15)
d -= 3;      // d = d - 3 (d is now 12)
d *= 2;      // d = d * 2 (d is now 24)
d /= 4;      // d = d / 4 (d is now 6)
d %= 4;      // d = d % 4 (d is now 2)

Comparison and Logical Operators

// Comparison Operators
let x = 10;
let y = "10";
let z = 5;

console.log(x == y);   // true (loose equality)
console.log(x === y);  // false (strict equality)
console.log(x != y);   // false (loose inequality)
console.log(x !== y);  // true (strict inequality)
console.log(x > z);    // true (greater than)
console.log(x >= z);   // true (greater than or equal)
console.log(x < z);    // false (less than)
console.log(x <= z);   // false (less than or equal)

// Logical Operators
let isActive = true;
let isAdmin = false;

console.log(isActive && isAdmin); // false (logical AND)
console.log(isActive || isAdmin); // true (logical OR)
console.log(!isActive);           // false (logical NOT)

// Short-circuit evaluation (like Python)
let username = null;
let displayName = username || "Guest"; // "Guest"

// Nullish coalescing operator (ES2020)
let count = 0;
let defaultCount = 10;
// In this case, 0 is a valid count, but is falsy
let finalCount = count || defaultCount; // 10 (undesired result)
// Nullish coalescing only falls back if value is null or undefined
let betterCount = count ?? defaultCount; // 0 (better result)

// Optional chaining (ES2020)
const user = { 
    profile: null 
};
// Without optional chaining
// console.log(user.profile.name); // Error: Cannot read property 'name' of null

// With optional chaining
console.log(user.profile?.name); // undefined (no error)

Python Comparison: JavaScript's dual equality operators (== vs ===) are a key difference from Python. Also, JavaScript uses && and || instead of Python's "and" and "or" keywords.

Bitwise and Other Operators

// Bitwise Operators
let a = 5;  // 101 in binary
let b = 3;  // 011 in binary

console.log(a & b);   // 1 (001 in binary) - Bitwise AND
console.log(a | b);   // 7 (111 in binary) - Bitwise OR
console.log(a ^ b);   // 6 (110 in binary) - Bitwise XOR
console.log(~a);      // -6 - Bitwise NOT
console.log(a << 1);  // 10 (1010 in binary) - Left shift
console.log(a >> 1);  // 2 (010 in binary) - Right shift

// Ternary Operator (conditional)
let age = 20;
let status = age >= 18 ? "Adult" : "Minor";
console.log(status); // "Adult"

// String Operators
let greeting = "Hello";
let name = "World";
console.log(greeting + " " + name); // "Hello World"

// The typeof operator
console.log(typeof 42);         // "number"
console.log(typeof "hello");    // "string"

Control Structures

JavaScript's control structures are syntactically different from Python but conceptually similar:

Conditional Statements

// if statement
let temperature = 75;

if (temperature > 90) {
    console.log("It's hot outside!");
} else if (temperature > 70) {
    console.log("It's warm outside.");
} else if (temperature > 50) {
    console.log("It's cool outside.");
} else {
    console.log("It's cold outside!");
}

// Python developers note: JavaScript uses curly braces {} instead of indentation
// and parentheses () around conditions

// Switch statement (no direct equivalent in Python)
let day = "Monday";

switch (day) {
    case "Monday":
        console.log("Start of the work week");
        break;  // Don't forget break or you'll fall through to the next case!
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
        console.log("Middle of the work week");
        break;
    case "Friday":
        console.log("End of the work week");
        break;
    case "Saturday":
    case "Sunday":
        console.log("Weekend!");
        break;
    default:
        console.log("Not a valid day");
        break;
}

The Truth About Truthiness: JavaScript considers these values as falsy:

  • false
  • 0
  • "" (empty string)
  • null
  • undefined
  • NaN (Not a Number)

Everything else is considered truthy, similar to Python's truth value testing.

Loops and Iterations

// for loop
for (let i = 0; i < 5; i++) {
    console.log(`Iteration ${i}`);
}

// while loop
let count = 0;
while (count < 5) {
    console.log(`Count is ${count}`);
    count++;
}

// do-while loop (always executes at least once)
let x = 0;
do {
    console.log(`x is ${x}`);
    x++;
} while (x < 3);

// for...in loop (iterates over object properties)
const person = {
    name: "Alice",
    age: 30,
    job: "Developer"
};

for (let key in person) {
    console.log(`${key}: ${person[key]}`);
}

// WARNING: for...in on arrays gives indices, not values
const colors = ["red", "green", "blue"];
for (let index in colors) {
    console.log(index); // "0", "1", "2" (as strings!)
    console.log(colors[index]); // "red", "green", "blue"
}

// for...of loop (ES6) - iterates over iterable values
for (let color of colors) {
    console.log(color); // "red", "green", "blue"
}

// for...of with objects requires Object.entries(), keys(), or values()
for (let [key, value] of Object.entries(person)) {
    console.log(`${key}: ${value}`);
}

// Array methods for iteration
colors.forEach((color, index) => {
    console.log(`${index}: ${color}`);
});

Python Comparison: JavaScript's traditional for loop is quite different from Python's for loop. The for...of loop is closest to Python's for loop syntax and behavior.

Loop Control

// break - exits the loop entirely
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break;
    }
    console.log(i); // Only logs 0 through 4
}

// continue - skips to the next iteration
for (let i = 0; i < 5; i++) {
    if (i === 2) {
        continue;
    }
    console.log(i); // Logs 0, 1, 3, 4 (skips 2)
}

// Labels (rarely used, but available)
outerLoop: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outerLoop; // Breaks out of both loops
        }
        console.log(`i=${i}, j=${j}`);
    }
}

Functions

Functions are first-class citizens in JavaScript, with several ways to define them:

Function Declarations and Expressions

// Function Declaration
function greet(name) {
    return `Hello, ${name}!`;
}

// Function Expression
const sayHello = function(name) {
    return `Hello, ${name}!`;
};

// Arrow Function (ES6)
const welcome = (name) => {
    return `Welcome, ${name}!`;
};

// Arrow Function with implicit return (single expression)
const hi = name => `Hi, ${name}!`;

// Immediately Invoked Function Expression (IIFE)
(function() {
    console.log("I run immediately!");
})();

// Key difference: Function declarations are hoisted, expressions are not
console.log(hoistedFunc()); // Works before declaration

function hoistedFunc() {
    return "I'm hoisted!";
}

// This would fail: console.log(notHoisted()); 
const notHoisted = function() {
    return "I'm not hoisted";
};

Theater Metaphor: If traditional functions are like actors with a script who can be called to stage multiple times, arrow functions are like voiceovers - they deliver the lines but don't bring their own context (this binding) to the performance.

Function Parameters

// Default parameters (ES6)
function createUser(name, role = "user", active = true) {
    return { name, role, active };
}

console.log(createUser("Alice")); // {name: "Alice", role: "user", active: true}
console.log(createUser("Bob", "admin")); // {name: "Bob", role: "admin", active: true}

// Rest parameters (ES6)
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// Destructuring parameters (ES6)
function displayPerson({ name, age, city = "Unknown" }) {
    console.log(`${name} is ${age} years old from ${city}`);
}

displayPerson({ name: "Charlie", age: 30, city: "New York" });
displayPerson({ name: "Dave", age: 25 }); // City defaults to "Unknown"

Scope and Closures

// Variable scope
let globalVar = "I'm global";

function scopeExample() {
    let functionVar = "I'm function-scoped";
    
    if (true) {
        let blockVar = "I'm block-scoped";
        var oldVar = "I'm function-scoped too!";
        console.log(globalVar);     // Accessible
        console.log(functionVar);   // Accessible
        console.log(blockVar);      // Accessible
    }
    
    console.log(oldVar);        // Accessible (var ignores blocks)
    // console.log(blockVar);    // Error! Not accessible outside block
}

// Closures - functions remember their lexical scope
function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// count variable is private and preserved between calls

// Closures with parameters
function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

Safe Metaphor: A closure is like a safe that contains variables from its surrounding environment. The function is the combination that opens the safe, allowing access to those variables even after the outer function has finished executing.

Python Comparison: JavaScript closures are similar to Python's function closures, but with different scoping rules around blocks.

The 'this' Keyword

One of the most confusing aspects of JavaScript for Python developers is the 'this' keyword:

// The value of 'this' depends on how a function is called
console.log(this); // In browser: Window object; In Node.js: global object

// In a method
const person = {
    name: "Alice",
    greet: function() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

person.greet(); // "Hello, my name is Alice"

// When the method is assigned to a variable
const greetFunc = person.greet;
// greetFunc(); // "Hello, my name is undefined" (this is no longer person)

// Using 'bind', 'call', or 'apply' to control 'this'
greetFunc.call(person); // "Hello, my name is Alice"

const otherPerson = { name: "Bob" };
person.greet.call(otherPerson); // "Hello, my name is Bob"

// Arrow functions and 'this'
const team = {
    members: ["Alice", "Bob"],
    leader: "Charlie",
    
    showMembers: function() {
        // Traditional function creates a new 'this' binding
        this.members.forEach(function(member) {
            // 'this' here refers to the global object, not team
            console.log(`${member} works with ${this.leader}`); // undefined leader
        });
        
        // Arrow function captures 'this' from surrounding context
        this.members.forEach(member => {
            // 'this' here still refers to team
            console.log(`${member} works with ${this.leader}`); // Charlie
        });
    }
};

team.showMembers();

Elastic Band Metaphor: Think of this as an elastic band that connects a function to an object. Regular functions create a new band, attached to whatever calls them. Arrow functions don't make their own band - they use the parent function's connection. The band can also be manually tied to objects using bind, call, or apply.

Python Comparison: This contrasts with Python's explicit self parameter in class methods. JavaScript's this is implicitly determined and can change based on call context.

Objects and Prototypes

JavaScript uses prototype-based inheritance rather than the class-based inheritance familiar to Python developers.

Creating Objects

// Object literal
const person = {
    firstName: "John",
    lastName: "Doe",
    age: 30,
    fullName: function() {
        return `${this.firstName} ${this.lastName}`;
    }
};

// Property access
console.log(person.firstName); // "John"
console.log(person["lastName"]); // "Doe"

// Dynamic properties
const key = "age";
console.log(person[key]); // 30

// Adding properties
person.email = "john@example.com";

// Deleting properties
delete person.age;

// Object property shorthand (ES6)
const name = "Alice";
const role = "developer";
const user = { name, role }; // Same as { name: name, role: role }

// Computed property names (ES6)
const propName = "job_title";
const employee = {
    [propName]: "Software Engineer"
};
console.log(employee.job_title); // "Software Engineer"

// Method shorthand (ES6)
const calculator = {
    add(a, b) {
        return a + b;
    },
    subtract(a, b) {
        return a - b;
    }
};

Prototype Inheritance

// Constructor function (pre-ES6 way to define "classes")
function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

// Adding methods to the prototype
Person.prototype.fullName = function() {
    return `${this.firstName} ${this.lastName}`;
};

// Creating instances
const john = new Person("John", "Doe");
const jane = new Person("Jane", "Smith");

console.log(john.fullName()); // "John Doe"
console.log(jane.fullName()); // "Jane Smith"

// Inheritance using prototypes
function Employee(firstName, lastName, position) {
    // Call the parent constructor
    Person.call(this, firstName, lastName);
    this.position = position;
}

// Set up prototype chain
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

// Add methods to Employee
Employee.prototype.getRole = function() {
    return `${this.fullName()} - ${this.position}`;
};

const alice = new Employee("Alice", "Johnson", "Developer");
console.log(alice.fullName()); // "Alice Johnson" (inherited method)
console.log(alice.getRole()); // "Alice Johnson - Developer"

Family Tree Metaphor: JavaScript's prototype inheritance is like a family tree. Objects inherit traits (properties and methods) from their prototype ancestor. When you look for a property, JavaScript first checks the object itself, then its parent, grandparent, and so on up the chain.

ES6 Classes

ES6 introduced a class syntax that will feel more familiar to Python developers, but under the hood it still uses prototypes:

// ES6 Class syntax
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
    
    // Static method (on the class, not instances)
    static createAnonymous() {
        return new Person("John", "Doe");
    }
}

const john = new Person("John", "Smith");
console.log(john.fullName()); // "John Smith"

// Inheritance with classes
class Employee extends Person {
    constructor(firstName, lastName, position) {
        super(firstName, lastName); // Call parent constructor
        this.position = position;
    }
    
    getRole() {
        return `${this.fullName()} - ${this.position}`;
    }
    
    // Override parent method
    fullName() {
        return `${this.lastName}, ${this.firstName}`;
    }
}

const alice = new Employee("Alice", "Johnson", "Developer");
console.log(alice.fullName()); // "Johnson, Alice" (overridden method)
console.log(alice.getRole()); // "Johnson, Alice - Developer"

Python Comparison: JavaScript's ES6 classes provide syntax similar to Python classes, but with important differences in how instantiation, inheritance, and private members work.

Arrays and Collections

Arrays are JavaScript's primary ordered collection type, similar to Python lists but with different methods and behaviors:

Array Basics

// Creating arrays
const numbers = [1, 2, 3, 4, 5];
const empty = [];
const mixed = [1, "two", { three: 3 }, [4, 5]];
const created = new Array(3); // [empty × 3]

// Accessing elements
console.log(numbers[0]); // 1
console.log(numbers[numbers.length - 1]); // 5

// Array properties
console.log(numbers.length); // 5

// Modifying arrays
numbers[1] = 20; // [1, 20, 3, 4, 5]

// JavaScript arrays can have "holes"
const sparse = [1, , 3]; // [1, empty, 3]
console.log(sparse.length); // 3

// Array can be modified even if declared with const
const arr = [1, 2, 3];
arr.push(4); // Works! (changes content, not reference)
// arr = [5, 6, 7]; // Error: Assignment to constant variable

Swiss Army Knife Metaphor: JavaScript arrays are like a Swiss Army knife - versatile tools that can hold different types of items, change size dynamically, and offer many built-in operations. Unlike Python's specialized tools (lists, tuples, sets), JavaScript arrays try to do a bit of everything.

Common Array Methods

const numbers = [1, 2, 3, 4, 5];
const fruits = ["apple", "banana", "cherry"];

// Mutating methods (change the original array)
numbers.push(6);     // Add to end: [1, 2, 3, 4, 5, 6]
numbers.pop();       // Remove from end: [1, 2, 3, 4, 5], returns 6
numbers.unshift(0);  // Add to beginning: [0, 1, 2, 3, 4, 5]
numbers.shift();     // Remove from beginning: [1, 2, 3, 4, 5], returns 0
numbers.splice(2, 1); // Remove 1 element at index 2: [1, 2, 4, 5]
numbers.splice(2, 0, 3); // Insert 3 at index 2: [1, 2, 3, 4, 5]
numbers.reverse();   // Reverse array: [5, 4, 3, 2, 1]
numbers.sort();      // Sort array: [1, 2, 3, 4, 5]

// Non-mutating methods (return new arrays)
const doubled = numbers.map(x => x * 2);    // [2, 4, 6, 8, 10]
const even = numbers.filter(x => x % 2 === 0); // [2, 4]
const sum = numbers.reduce((acc, x) => acc + x, 0); // 15
const allPositive = numbers.every(x => x > 0); // true
const hasEven = numbers.some(x => x % 2 === 0); // true
const found = numbers.find(x => x > 3); // 4
const foundIndex = numbers.findIndex(x => x > 3); // 3

// Searching in arrays
const cherryIndex = fruits.indexOf("cherry"); // 2
const bananaIncludes = fruits.includes("banana"); // true

// Combining arrays
const combined = numbers.concat(fruits); // [1, 2, 3, 4, 5, "apple", "banana", "cherry"]
const sliced = numbers.slice(1, 4); // [2, 3, 4] (start index, end index exclusive)

// Converting to string
const joined = fruits.join(", "); // "apple, banana, cherry"

// ES6+ array methods
const flattenedArray = [1, [2, 3], [4, [5]]].flat(); // [1, 2, 3, 4, [5]]
const deepFlattenedArray = [1, [2, 3], [4, [5]]].flat(Infinity); // [1, 2, 3, 4, 5]
const entries = fruits.entries(); // Iterator with [index, value] pairs

Python Comparison: Many of these methods are similar to Python's list methods, but with different names (e.g., append vs push) and behaviors (e.g., splice has no direct Python equivalent).

Maps, Sets, and Other Collections

// Sets (ES6) - collections of unique values
const uniqueNumbers = new Set([1, 2, 3, 2, 1]); // {1, 2, 3}
uniqueNumbers.add(4); // {1, 2, 3, 4}
uniqueNumbers.delete(2); // {1, 3, 4}
console.log(uniqueNumbers.has(3)); // true
console.log(uniqueNumbers.size); // 3

// Maps (ES6) - key-value pairs where keys can be any type
const userRoles = new Map();
userRoles.set("john", "admin");
userRoles.set("jane", "user");
userRoles.set(john, "moderator"); // Object as key
console.log(userRoles.get("john")); // "admin"
console.log(userRoles.has("jane")); // true
userRoles.delete("jane");
console.log(userRoles.size); // 1

// Weak collections (WeakMap, WeakSet)
// Allow garbage collection of their keys
const weakMap = new WeakMap();
weakMap.set(john, "user data");
// When john object is no longer referenced elsewhere, it can be garbage collected

Python Comparison: JavaScript's Map is similar to Python's dict but allows any type as keys. JS Set is similar to Python's set but with different method names.

Error Handling

JavaScript uses a try-catch mechanism for error handling, similar to Python's try-except:

Try-Catch-Finally

// Basic try-catch
try {
    // Code that might throw an error
    const result = nonExistentFunction();
    console.log(result);
} catch (error) {
    // Handle the error
    console.error("An error occurred:", error.message);
}

// Try-catch-finally
try {
    console.log("Attempting risky operation");
    if (Math.random() > 0.5) {
        throw new Error("Random failure");
    }
    console.log("Operation succeeded");
} catch (error) {
    console.error("Operation failed:", error.message);
} finally {
    console.log("Cleanup code - runs regardless of success or failure");
}

// Catching specific error types
try {
    JSON.parse("invalid json");
} catch (error) {
    if (error instanceof SyntaxError) {
        console.error("JSON parsing error:", error.message);
    } else {
        console.error("Other error:", error.message);
    }
}

// Creating custom errors
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

function validateUser(user) {
    if (!user.name) {
        throw new ValidationError("Name is required");
    }
    if (!user.email) {
        throw new ValidationError("Email is required");
    }
    return true;
}

try {
    validateUser({ name: "Alice" });
} catch (error) {
    if (error instanceof ValidationError) {
        console.error("Validation failed:", error.message);
    } else {
        console.error("Unknown error:", error);
    }
}

Python Comparison: JavaScript's try-catch is similar to Python's try-except, but with different syntax. JavaScript doesn't have a built-in way to catch specific error types by listing them, so instanceof checks are used.

Asynchronous JavaScript Basics

JavaScript's asynchronous nature is a fundamental concept that differs from Python's primarily synchronous execution model.

Callbacks (Traditional)

// Callbacks are functions passed as arguments to be executed later
function getUserData(userId, callback) {
    // Simulate async operation (e.g., API request)
    setTimeout(() => {
        const user = { id: userId, name: "Alice", role: "admin" };
        callback(user);
    }, 1000);
}

getUserData(123, function(user) {
    console.log("User data received:", user);
});

console.log("This runs before user data is received");

// Callback hell - nested callbacks
getUserData(123, function(user) {
    console.log("Got user:", user.name);
    
    getPermissions(user.role, function(permissions) {
        console.log("Got permissions:", permissions);
        
        getFeatureAccess(permissions, function(features) {
            console.log("Got features:", features);
            
            // More nested callbacks...
        });
    });
});

Restaurant Metaphor: Asynchronous JavaScript with callbacks is like ordering at a busy restaurant. After placing your order (making a request), you get a buzzer (the callback function) instead of waiting at the counter. You're free to do other things (execute other code), and when your order is ready, the buzzer calls you back to pick it up (the callback executes).

Promises (ES6)

// Promises represent a value that may not be available yet
function getUserData(userId) {
    return new Promise((resolve, reject) => {
        // Simulate async operation
        setTimeout(() => {
            const user = { id: userId, name: "Alice", role: "admin" };
            resolve(user); // Success case
            
            // For error case: reject(new Error("User not found"));
        }, 1000);
    });
}

// Using promises
getUserData(123)
    .then(user => {
        console.log("Got user:", user.name);
        return getPermissions(user.role); // Return another promise
    })
    .then(permissions => {
        console.log("Got permissions:", permissions);
        return getFeatureAccess(permissions); // Return another promise
    })
    .then(features => {
        console.log("Got features:", features);
    })
    .catch(error => {
        console.error("Error in promise chain:", error.message);
    })
    .finally(() => {
        console.log("Promise chain completed");
    });
    
// Promise utility methods
Promise.all([getUserData(123), getPermissions("admin")])
    .then(([user, permissions]) => {
        console.log("Both promises resolved:", user, permissions);
    });
    
Promise.race([fastAPI(), slowAPI()])
    .then(result => {
        console.log("Fastest API returned:", result);
    });

Async/Await (ES2017)

// Async functions always return promises
async function getUserInfo(userId) {
    try {
        // Await pauses execution until the promise resolves
        const user = await getUserData(userId);
        console.log("Got user:", user.name);
        
        const permissions = await getPermissions(user.role);
        console.log("Got permissions:", permissions);
        
        const features = await getFeatureAccess(permissions);
        console.log("Got features:", features);
        
        return { user, permissions, features };
    } catch (error) {
        console.error("Error in async function:", error.message);
        throw error; // Re-throw or handle
    }
}

// Using async function
getUserInfo(123)
    .then(result => {
        console.log("All data:", result);
    })
    .catch(error => {
        console.error("Caught outside:", error.message);
    });
    
// Parallel operations with async/await
async function getMultipleUsers() {
    const userPromises = [getUserData(1), getUserData(2), getUserData(3)];
    const users = await Promise.all(userPromises);
    return users;
}

Python Comparison: Async/await in JavaScript is conceptually similar to Python's asyncio syntax, but with different execution model details.

Modules and Imports

JavaScript has evolved multiple module systems, with ES6 modules now being the standard:

ES6 Modules

// math.js - Exporting functionality
export const PI = 3.14159;

export function square(x) {
    return x * x;
}

export function cube(x) {
    return x * x * x;
}

// Default export - only one per module
export default function multiply(a, b) {
    return a * b;
}

// app.js - Importing functionality
import multiply, { PI, square, cube } from './math.js';

console.log(PI);            // 3.14159
console.log(square(4));     // 16
console.log(cube(3));       // 27
console.log(multiply(2, 5)); // 10

// Renaming imports
import { square as getSquare } from './math.js';
console.log(getSquare(4));  // 16

// Importing everything
import * as Math from './math.js';
console.log(Math.PI);       // 3.14159
console.log(Math.square(4)); // 16
console.log(Math.default(2, 5)); // 10 (default export is under .default)

Python Comparison: ES6 modules are similar to Python's import system, but with a different syntax. JavaScript distinguishes between default and named exports, unlike Python.

Older Module Systems

// CommonJS (Node.js) - still widely used in Node.js
// math.js
const PI = 3.14159;

function square(x) {
    return x * x;
}

module.exports = {
    PI,
    square,
    multiply: function(a, b) {
        return a * b;
    }
};

// app.js
const math = require('./math.js');
console.log(math.PI);        // 3.14159
console.log(math.square(4)); // 16

// Destructuring in require
const { PI, multiply } = require('./math.js');
console.log(multiply(2, 5)); // 10

Debugging and Developer Tools

JavaScript offers several ways to debug and inspect your code:

Console Methods

// Basic logging
console.log("Hello, world!"); // General logging
console.info("Information"); // Informational message
console.warn("Warning"); // Warning message (yellow in most consoles)
console.error("Error!"); // Error message (red in most consoles)

// Structured output
console.table([
    { name: "Alice", age: 25 },
    { name: "Bob", age: 30 }
]); // Displays as table

// Grouping logs
console.group("User Details");
console.log("Name: Alice");
console.log("Role: Admin");
console.groupEnd();

// Timing operations
console.time("operation");
// ... some operation
console.timeEnd("operation"); // "operation: 1.23ms"

// Log with variable substitution
console.log("User %s logged in from %s", "Alice", "New York");

// Conditional logging
console.assert(1 === 2, "This will log because the assertion failed");

Browser Developer Tools

  • Debugger statement: Add debugger; in your code to create a breakpoint
  • Source panel: Set breakpoints, step through code, examine call stack
  • Console panel: Interactive REPL to test code and see outputs
  • Network panel: Monitor network requests and responses
  • Performance panel: Analyze execution time and memory usage

JavaScript vs Python: Key Differences Summary

Feature JavaScript Python
Syntax Structure Curly braces for blocks Indentation for blocks
Function Definition function name() {} or const name = () => {} def name():
Variable Declaration Requires var, let, or const Direct assignment declares variables
Scoping Block-scoped (let/const) or function-scoped (var) Function-scoped with some block scope (modern Python)
Arrays/Lists Single Array type with many methods Multiple sequence types (list, tuple, etc.)
Objects/Dictionaries Objects with prototype inheritance Dictionary type with key-value pairs
Equality Testing == (with coercion) and === (strict) Only == (strict by default)
Inheritance Model Prototype-based (even when using class syntax) Class-based
Asynchronous Code Single-threaded with callbacks, promises, async/await Multi-threaded or asyncio
Logical Operators &&, ||, ! and, or, not
Error Handling try/catch/finally try/except/finally
Method Context Implicit this, context can change Explicit self parameter

Modern JavaScript Practices

When writing modern JavaScript, consider these practices used by professional developers:

Modern Code Example

// Modern JavaScript Example

// ES6 Modules
import { api } from './api.js';

// Arrow functions 
const getUser = async (id) => {
    try {
        // Template literals
        const url = `/users/${id}`;
        
        // Async/await
        const response = await api.get(url);
        
        // Destructuring
        const { name, email, role = 'user' } = response.data;
        
        // Shorthand properties and computed properties
        const timestamp = Date.now();
        const user = {
            id,
            name,
            email,
            role,
            [`lastAccessed_${timestamp}`]: new Date().toISOString()
        };
        
        // Rest/spread operators
        const publicData = { ...user };
        delete publicData.email;
        
        return publicData;
    } catch (error) {
        // Enhanced error objects in ES6+
        console.error(`Error fetching user ${id}:`, error?.message);
        throw error;
    }
};

// Array methods
const users = [
    { id: 1, name: 'Alice', active: true },
    { id: 2, name: 'Bob', active: false },
    { id: 3, name: 'Charlie', active: true }
];

// Find, filter, map, reduce
const activeUsers = users.filter(user => user.active);
const userNames = activeUsers.map(user => user.name);
const usersByName = users.reduce((acc, user) => {
    acc[user.name] = user;
    return acc;
}, {});

Practice Exercises

Exercise 1: Basic JavaScript Syntax

Write JavaScript equivalents for the following Python code snippets:

  1. A function that calculates factorial
  2. Filtering a list of numbers to get only the even ones
  3. A class representing a Circle with radius, area, and circumference
  4. A dict comprehension that creates a dictionary with numbers as keys and their squares as values

Exercise 2: Data Transformation

Given a list of users with name, age, and role properties:

  1. Filter to get only adults (age >= 18)
  2. Transform to get an array of just names
  3. Create an object where keys are roles and values are arrays of users with that role
  4. Find the average age of all users

Implement using both traditional loops and modern array methods.

Exercise 3: Async JavaScript

Create a fake API with setTimeout that simulates fetching users, posts, and comments. Then:

  1. Implement with callbacks
  2. Refactor to use Promises
  3. Refactor again to use async/await
  4. Add proper error handling to all implementations

Lecture Summary

In this JavaScript fundamentals review, we've covered:

This foundation will serve as the building blocks for our DOM manipulation and event handling sessions, which will enable you to create dynamic and interactive web interfaces. Remember that as Python developers, you already understand many programming concepts - the challenge is mostly about adapting to JavaScript's syntax and behavior differences.