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:
- 1995: Created by Brendan Eich at Netscape in just 10 days, initially named "Mocha"
- 1996: Renamed to "JavaScript" (despite having no relation to Java)
- 1997: Standardized as ECMAScript (ES1)
- 1999-2009: Slow evolution (ES3, ES4 abandoned)
- 2009: ES5 introduced with important new features
- 2015: ES6/ES2015 - A major update with significant new capabilities
- 2016-Present: Yearly releases (ES2016, ES2017, etc.) with incremental improvements
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:
false0""(empty string)nullundefinedNaN(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:
- Use ES6+ Features: Leverage arrow functions, template literals, destructuring, and other modern syntax
- Prefer let/const over var: Use block scoping for clearer code and fewer bugs
- Destructuring: Extract values from objects and arrays concisely
- Spread/Rest Operators: Use
...for flexible function parameters and array/object manipulation - Async/Await: Make asynchronous code more readable
- Default Parameters: Provide fallback values in function definitions
- Modules: Organize code into ES6 modules
- Shorthand Properties: Use
{ name }instead of{ name: name }
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:
- A function that calculates factorial
- Filtering a list of numbers to get only the even ones
- A class representing a Circle with radius, area, and circumference
- 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:
- Filter to get only adults (age >= 18)
- Transform to get an array of just names
- Create an object where keys are roles and values are arrays of users with that role
- 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:
- Implement with callbacks
- Refactor to use Promises
- Refactor again to use async/await
- Add proper error handling to all implementations
Lecture Summary
In this JavaScript fundamentals review, we've covered:
- JavaScript's evolution and modern features
- Variables, data types, and type coercion
- Operators and expressions
- Control structures (conditionals and loops)
- Functions, scope, and closures
- Objects, prototypes, and ES6 classes
- Arrays and collection methods
- Error handling with try-catch
- Asynchronous JavaScript basics
- Modules and imports
- Debugging tools and techniques
- Comparisons with Python for full-stack developers
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.