Interactive JavaScript Learning Guide

From "Hello World" to "Async/Await", learn the programming language of the web.

1. Introduction & Setup

JavaScript (JS) makes websites interactive. It's the "muscles" that complement HTML (skeleton) and CSS (skin). It runs in the user's browser and can manipulate HTML, change CSS, and handle user actions.

You can add JS to an HTML file in three ways:

  • Internal: Inside a <script> tag (usually at the end of the <body>).
  • External: In a separate .js file, linked with <script src="app.js"></script>. (Best practice)
  • Inline: Directly in an HTML attribute like onclick="". (Not recommended)

In these examples, the code runs as if it's in an internal <script> tag. We use console.log() to print output to the developer console (Press F12 to see it).

2. Variables & Data Types

Variables are containers for storing data. Use let for variables that can change and const for variables that cannot (constants). var is the old way and should generally be avoided.

JS has several core data types: String (text), Number (integers and decimals), Boolean (true/false), Object, Array, null, and undefined.

// Use let for variables that can be reassigned
let age = 30;
age = 31; // This is fine

// Use const for constants
const name = "Alice";
// name = "Bob"; // This would cause an error!

// Data Types
let message: "Hello!";       // String
let pi: 3.14;             // Number
let isLearning: true;   // Boolean
let nothing: null;         // Null
let notDefined;           // Undefined

console.log("Name:", name);
console.log("Age:", age);
console.log("Type of pi:", typeof pi);

3. Operators

Operators perform operations on values. This includes arithmetic (+, -, *, /, %), assignment (=, +=), comparison (>, <, ===), and logical (&& AND, || OR) operators.

Tip: Always use **strict equality** === (checks value and type) instead of loose equality == (which performs type coercion).

let a = 10;
let b = 4;

console.log("10 + 4 =", a + b);
console.log("10 % 4 =", a % b); // Modulo (remainder)

console.log("Strict: 10 === '10'?", 10 === '10'); // false
console.log("Loose: 10 == '10'?", 10 == '10'); // true (avoid this!)

let isSunny = true;
let isWarm = false;
console.log("Go to beach?", isSunny && isWarm); // false
console.log("Go outside?", isSunny || isWarm); // true

4. Functions

Functions are reusable blocks of code. They can take inputs (parameters) and give back an output (return value). There are several ways to write them:

// 1. Function Declaration
function greet(name) {
  return `Hello, ${name}!`; // Using a template literal
}
console.log(greet("Alice"));

// 2. Function Expression
const add = function(a, b) {
  return a + b;
};
console.log("Sum:", add(5, 3));

// 3. Arrow Function (ES6)
const subtract = (a, b) => a - b;
console.log("Difference:", subtract(10, 4));

5. Scope

Scope determines the accessibility of variables. Variables declared outside any function are **Global**. Variables declared inside a function are **Function-Scoped** (if using var) or **Block-Scoped** (if using let or const). Block scope (any { ... }) is more predictable and preferred.

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

function testScope() {
  let functionVar = "I'm in a function";
  console.log(globalVar); // Can access global

  if (true) {
    let blockVar = "I'm in a block";
    console.log(blockVar);
  }
  
  // console.log(blockVar); // Error! blockVar is not defined here
}
testScope();
// console.log(functionVar); // Error! functionVar is not defined here

6. Conditionals (if/switch)

Conditionals let your code make decisions. if...else is used for most cases. switch is a useful alternative when checking a single variable against many possible values.

let day = "Monday";

if (day === "Saturday" || day === "Sunday") {
  console.log("It's the weekend! 🥳");
} else {
  console.log("It's a weekday.");
}

let color = "blue";
switch (color) {
  case "red":
    console.log("Color is red");
    break;
  case "blue":
    console.log("Color is blue");
    break;
  default:
    console.log("Color is not red or blue");
}

7. Loops (for/while)

Loops run code repeatedly. A for loop is great when you know how many times to loop. A while loop is for when you want to loop as long as a condition is true. A for...of loop is the easiest way to iterate over an array.

// 1. 'for' loop (from 0 up to 2)
console.log("For Loop:");
for (let i = 0; i < 3; i++) {
  console.log(i);
}

// 2. 'while' loop
console.log("While Loop:");
let count = 0;
while (count < 3) {
  console.log(count);
  count++;
}

// 3. 'for...of' loop (for arrays)
console.log("For...of Loop:");
const colors = ['red', 'green'];
for (const color of colors) {
  console.log(color);
}

8. Arrays

Arrays are ordered lists of values. You can access items by their index (starting at 0) and use properties like .length and methods like .push() (add to end) and .pop() (remove from end).

const fruits = ['apple', 'banana', 'orange'];

// Get by index (0-based)
console.log(fruits[0]); // 'apple'

// Get length
console.log("Length:", fruits.length); // 3

// Add an item to the end
fruits.push('grape');
console.log(fruits);

// Remove an item from the end
fruits.pop();
console.log(fruits);

9. Objects

Objects are collections of key-value pairs. They are perfect for grouping related data. You can access properties using dot notation (.) or bracket notation ([]).

const person = {
  firstName: 'Jane',
  lastName: 'Doe',
  age: 28,
  sayHello: function() { // A method
    console.log('Hello!');
  }
};

// Access with dot notation
console.log(person.firstName);

// Access with bracket notation (useful for variables)
let prop = 'lastName';
console.log(person[prop]);

// Call the method
person.sayHello();

10. Array Methods (map, filter)

ES6 introduced powerful methods for working with arrays. .forEach() loops over each item, .map() creates a *new* array by transforming each item, and .filter() creates a *new* array with only the items that pass a test.

const numbers = [1, 2, 3, 4, 5];

// .forEach() - just loops
numbers.forEach((num) => {
  console.log(`Number is ${num}`);
});

// .map() - creates a new array
const doubled = numbers.map((num) => {
  return num * 2;
});
console.log("Doubled:", doubled);

// .filter() - creates a new filtered array
const evens = numbers.filter((num) => {
  return num % 2 === 0;
});
console.log("Evens:", evens);

11. The DOM

The **DOM (Document Object Model)** is a tree-like representation of your HTML page. JavaScript can read and change the DOM, allowing you to make your page dynamic. Think of the document object as the entry point to the entire page.

12. Selecting Elements

To manipulate an element, you must first select it. The modern way is to use document.querySelector() (for one element) and document.querySelectorAll() (for all matching elements). These use CSS selectors, making them very powerful.

// Old way: by ID
const title1 = document.getElementById('title');

// Modern way: by CSS selector (ID)
const title2 = document.querySelector('#title');
console.log(title2);

// Select the FIRST matching element
const firstP = document.querySelector('.content');
console.log(firstP);

// Select ALL matching elements (returns a NodeList)
const allPs = document.querySelectorAll('.content');
console.log(allPs);
console.log(allPs[1]); // Access the second one

13. Manipulating Elements

Once you select an element, you can change it! You can alter its text (.textContent), its HTML (.innerHTML), its style (.style), its classes (.classList), or even create new elements from scratch (document.createElement).

const title = document.querySelector('#title');
const app = document.querySelector('#app');

// 1. Change text
title.textContent = 'Hello, JavaScript!';

// 2. Change style
title.style.color = '#5E35B1';

// 3. Add/Remove CSS classes
title.classList.add('highlight');

// 4. Create and append a new element
const newP = document.createElement('p');
newP.textContent = 'This paragraph was added by JS.';
app.appendChild(newP);

14. Events & Listeners

Events are how your page responds to user actions. You "listen" for an event (like a 'click') on an element. When the event happens, your "callback function" runs.

const button = document.querySelector('#myButton');
const message = document.querySelector('#message');

function handleClick() {
  message.textContent = 'Button was clicked!';
}

// Attach the function to the 'click' event
button.addEventListener('click', handleClick);

// You can also use an anonymous arrow function
message.addEventListener('mouseover', () => {
  message.style.color = 'red';
});

15. Handling Forms

A common task is getting data from a form. You listen for the 'submit' event on the form element. Inside the listener, you must call event.preventDefault() to stop the page from reloading. Then you can get the values from the input fields.

const form = document.querySelector('#loginForm');
const input = document.querySelector('#nameInput');
const greeting = document.querySelector('#greeting');

form.addEventListener('submit', (event) => {
  // Stop the page from reloading!
  event.preventDefault();
  
  // Get the value from the input
  const userName = input.value;
  
  // Use the value
  greeting.textContent = `Welcome, ${userName}!`;
  
  // Clear the input
  input.value = '';
});

16. ES6+ Features

Modern JS has many features that make coding cleaner. **Template Literals** (`...`) let you embed variables in strings. **Destructuring** lets you unpack values from arrays or objects. The **Spread Operator** (...) lets you copy or merge arrays and objects.

// 1. Template Literals
const name = 'Leo';
console.log(`My name is ${name}.`);

// 2. Object Destructuring
const user = { id: 1, email: 'test@user.com' };
const { id, email } = user;
console.log(`User ID is ${id}`);

// 3. Array Destructuring
const [first, second] = ['a', 'b', 'c'];
console.log(`First is ${first}, second is ${second}`);

// 4. Spread Operator
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4]; // [1, 2, 3, 4]
console.log(arr2);

17. 'this' & Classes

The this keyword refers to the object *that is executing the current function*. In an object method, this refers to the object itself. The class syntax is a modern way to create "blueprint" for objects.

class User {
  // Runs when a new User is created
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // This is a method
  greet() {
    // 'this' refers to the instance (p1 or p2)
    console.log(`Hi, I'm ${this.name} and I'm ${this.age}.`);
  }
}

// Create "instances" of the User class
const p1 = new User('Maria', 32);
const p2 = new User('Ben', 25);

p1.greet(); // 'this' is p1
p2.greet(); 'this' is p2

18. Asynchronous JS

JavaScript is **single-threaded**, meaning it can only do one thing at a time. Asynchronous (or "async") code allows us to start a long-running task (like downloading a file) and *continue with other code* while we wait. When the task finishes, it runs our "callback" function.

setTimeout is a simple way to see this in action. Notice how "End" logs before "After 2 seconds".

console.log("Start");

// This is async. It waits 2s (2000ms)
setTimeout(() => {
  console.log("After 2 seconds...");
}, 2000);

// This code does NOT wait! It runs immediately.
console.log("End");

19. Promises

A **Promise** is an object that represents the eventual completion (or failure) of an asynchronous task. It's a "promise" to give you a value later. You handle the successful result with .then() and any errors with .catch(). This avoids "callback hell" (nesting callbacks inside callbacks).

const willItRain = new Promise((resolve, reject) => {
  // Simulate a 1s delay
  setTimeout(() => {
    const isRaining = false;
    if (isRaining) {
      reject("It's raining! ☔");
    } else {
      resolve("It's sunny! ☀️");
    }
  }, 1000);
});

console.log("Checking weather...");

willItRain
  .then((message) => {
    // This runs if it resolves (succeeds)
    console.log("Success:", message);
  })
  .catch((error) => {
    // This runs if it rejects (fails)
    console.log("Failure:", error);
  });

20. Async/Await & Fetch API

async/await is modern syntax that makes Promises *much* easier to read. The fetch() API is the modern way to request data from a server (an API) and it returns a Promise.

An async function can use the await keyword to "pause" and wait for a Promise to resolve, without blocking the whole program. You use try...catch to handle errors.

// We must wrap 'await' in an 'async' function
async function getTodo() {
  console.log('Fetching todo...');
  try {
    // 1. Await the fetch call
    const response = await fetch(
      'https://jsonplaceholder.typicode.com/todos/1'
    );
    // 2. Await parsing the JSON data
    const data = await response.json();
    
    console.log('Todo title:', data.title);
    
  } catch (error) {
    // Handle any errors
    console.log('Error!', error);
  }
}

getTodo();

21. JavaScript Final Project: Random Quote Generator

Let's combine everything to build an app that fetches a random quote from an API and displays it. This will use async/await, fetch, DOM selection, and an event listener.

// 1. Select Elements
const quoteEl = document.querySelector('#quote');
const authorEl = document.querySelector('#author');
const btn = document.querySelector('#newQuoteBtn');

// 2. Create the API function
async function getQuote() {
  quoteEl.textContent = 'Loading...';
  authorEl.textContent = '';
  
  try {
    const response = await fetch('https://api.quotable.io/random');
    const data = await response.json();

    // 3. Update the DOM
    quoteEl.textContent = data.content;
    authorEl.textContent = `- ${data.author}`;

  } catch (error) {
    quoteEl.textContent = 'Failed to fetch quote.';
    console.log(error);
  }
}

// 4. Add event listener
btn.addEventListener('click', getQuote);

22. Live Practice Editor

Use this space to experiment. Write your HTML and JavaScript, then press "Run Code". See the preview on the right and check the console for console.log() messages.

HTML Code

JavaScript Code

Live Preview

Console Output