Lesson 4 of 107 min read

JavaScript Basics

Share:WhatsAppLinkedIn

What you'll build

By the end of this lesson you will have two working mini-projects: a click counter button and a to-do list where you can add items and mark them done. Both live in a single HTML file with vanilla JavaScript, no libraries, no build step. You will understand every line.

Concepts

Variables, types, and operators

JavaScript has three ways to declare variables. Use const by default. Use let when you need to reassign. Never use var in new code, it has scoping quirks that trip up everyone.

const name = "Ananya";        // string
const age = 22;               // number
const isStudent = true;       // boolean
const nothing = null;         // null (intentional absence of value)
let score = 0;                // let because we will change this

// typeof tells you the type at runtime
console.log(typeof name);     // "string"
console.log(typeof age);      // "number"
console.log(typeof nothing);  // "object", this is a famous JS quirk

// Template literals (backtick strings) support interpolation
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// Hello, Ananya! You are 22 years old.

JavaScript uses === for equality checks. Never use ==, it does type coercion in ways that produce surprising results.

console.log(1 == "1");   // true, coerces types, usually not what you want
console.log(1 === "1");  // false, checks value AND type
console.log(1 === 1);    // true

Control flow and functions

// if / else if / else
const hour = new Date().getHours();
let greeting;

if (hour < 12) {
  greeting = "Good morning";
} else if (hour < 17) {
  greeting = "Good afternoon";
} else {
  greeting = "Good evening";
}

// for loop
for (let i = 0; i < 5; i++) {
  console.log(i); // 0, 1, 2, 3, 4
}

// Arrow function (preferred for short functions)
const double = (n) => n * 2;
console.log(double(7)); // 14

// Regular function (use when you need `this` or recursion by name)
function greet(name) {
  return `Hello, ${name}!`;
}

// Functions are values, you can pass them as arguments
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(double); // [2, 4, 6, 8, 10]

Arrays and objects

Arrays hold ordered lists. Objects hold key-value pairs.

// Arrays
const fruits = ["mango", "banana", "papaya"];
console.log(fruits[0]);          // "mango"
console.log(fruits.length);      // 3

fruits.push("guava");            // add to end
fruits.pop();                    // remove from end
const upper = fruits.map(f => f.toUpperCase()); // ["MANGO", "BANANA", "PAPAYA"]
const filtered = fruits.filter(f => f.length > 5); // ["banana", "papaya"]

// Objects
const student = {
  name: "Rohan",
  grade: 10,
  subjects: ["Maths", "Science"],
  address: {
    city: "Bengaluru",
    state: "Karnataka"
  }
};

console.log(student.name);            // "Rohan"
console.log(student["grade"]);        // 10, bracket notation also works
console.log(student.address.city);    // "Bengaluru"

// Destructuring, extract values into named variables
const { name, grade } = student;
const [firstSubject] = student.subjects;
console.log(name, grade, firstSubject); // "Rohan" 10 "Maths"

// Spread to copy or merge
const updated = { ...student, grade: 11 }; // new object with grade changed

DOM manipulation

The DOM (Document Object Model) is the browser's in-memory tree of your HTML. JavaScript can read and change it.

// Select elements
const heading = document.querySelector("h1");         // first match
const allButtons = document.querySelectorAll("button"); // NodeList of all buttons

// Read and change content
heading.textContent = "New heading text";
heading.innerHTML = "<em>Italic heading</em>"; // use with caution, XSS risk

// Change styles and classes
heading.style.color = "royalblue";
heading.classList.add("active");
heading.classList.remove("hidden");
heading.classList.toggle("highlight");

// Create and insert new elements
const li = document.createElement("li");
li.textContent = "New list item";
document.querySelector("ul").appendChild(li);

// Events
const button = document.querySelector("#myButton");
button.addEventListener("click", (event) => {
  console.log("Button clicked!", event.target);
});

querySelector uses the same syntax as CSS selectors: "#id", ".class", "tag", "parent > child". This is the one method you need for 90% of DOM work.

Hands-on

Build both mini-projects in one file. Create todo.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS Mini Projects</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 500px; margin: 40px auto; padding: 0 16px; }
    button { padding: 8px 16px; cursor: pointer; margin: 4px; }
    #count { font-size: 3rem; font-weight: bold; margin: 8px 0; }
    .todo-form { display: flex; gap: 8px; margin: 16px 0; }
    .todo-form input { flex: 1; padding: 8px; font-size: 1rem; }
    .todo-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid #eee; }
    .todo-item.done span { text-decoration: line-through; color: #999; }
    .todo-item button { background: #fee; border: 1px solid #fcc; }
  </style>
</head>
<body>

  <h1>Counter</h1>
  <div id="count">0</div>
  <button id="decrement">-</button>
  <button id="reset">Reset</button>
  <button id="increment">+</button>

  <hr>

  <h1>To-Do List</h1>
  <form class="todo-form" id="todo-form">
    <input type="text" id="todo-input" placeholder="Add a task..." required minlength="1">
    <button type="submit">Add</button>
  </form>
  <ul id="todo-list"></ul>

  <script>
    // ---- Counter ----
    let count = 0;
    const countDisplay = document.querySelector("#count");

    function updateCount(newValue) {
      count = newValue;
      countDisplay.textContent = count;
    }

    document.querySelector("#increment").addEventListener("click", () => {
      updateCount(count + 1);
    });

    document.querySelector("#decrement").addEventListener("click", () => {
      updateCount(count - 1);
    });

    document.querySelector("#reset").addEventListener("click", () => {
      updateCount(0);
    });

    // ---- To-Do List ----
    const todoForm = document.querySelector("#todo-form");
    const todoInput = document.querySelector("#todo-input");
    const todoList = document.querySelector("#todo-list");

    // Each todo is an object: { id, text, done }
    let todos = [];

    function renderTodos() {
      todoList.innerHTML = "";

      todos.forEach((todo) => {
        const li = document.createElement("li");
        li.className = "todo-item" + (todo.done ? " done" : "");

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.checked = todo.done;
        checkbox.addEventListener("change", () => toggleTodo(todo.id));

        const span = document.createElement("span");
        span.textContent = todo.text;

        const deleteBtn = document.createElement("button");
        deleteBtn.textContent = "Delete";
        deleteBtn.addEventListener("click", () => deleteTodo(todo.id));

        li.appendChild(checkbox);
        li.appendChild(span);
        li.appendChild(deleteBtn);
        todoList.appendChild(li);
      });
    }

    function addTodo(text) {
      const newTodo = {
        id: Date.now(),   // unique enough for a demo
        text: text,
        done: false
      };
      todos = [...todos, newTodo];
      renderTodos();
    }

    function toggleTodo(id) {
      todos = todos.map((t) =>
        t.id === id ? { ...t, done: !t.done } : t
      );
      renderTodos();
    }

    function deleteTodo(id) {
      todos = todos.filter((t) => t.id !== id);
      renderTodos();
    }

    todoForm.addEventListener("submit", (event) => {
      event.preventDefault();           // stop the page from reloading
      const text = todoInput.value.trim();
      if (text) {
        addTodo(text);
        todoInput.value = "";           // clear the input after adding
      }
    });
  </script>

</body>
</html>

Open this in your browser. The counter buttons update the number immediately. The to-do form adds items with a checkbox and delete button. Notice:

  • event.preventDefault() stops the form from doing a full-page reload when submitted., Each todo has a unique id (using Date.now()) so we can find and update the right one without relying on array index, which changes when items are deleted., We never directly mutate the todos array inside toggleTodo and deleteTodo. We create new arrays with .map() and .filter(), then reassign todos. This pattern makes state changes predictable and is the foundation of how React works.

Common pitfalls

  • Using == instead of ===. The loose equality operator coerces types before comparing. 0 == false is true in JavaScript. Always use === unless you deliberately want type coercion, which is almost never.
  • Accessing DOM before it loads. If your <script> tag is in <head> and you call document.querySelector("#myButton"), the element does not exist yet. Either put scripts at the bottom of <body>, or use DOMContentLoaded. The easiest fix is to place <script> just before </body>.
  • Forgetting event.preventDefault() on form submit. Without it, the browser reloads the page on submit, wiping all your JavaScript state. Always call it in any submit handler.
  • Directly mutating arrays and objects in state. Code like todos.push(newItem) mutates the array in place. This works for plain scripts but breaks React and other frameworks that detect state changes by reference. Get in the habit of creating new values with spread and .map()/.filter().
  • typeof null === "object". This is a decades-old bug in JavaScript that was never fixed for compatibility reasons. To check for null, use value === null, not typeof value === "object".

What to try next

  1. Add a "Clear completed" button to the to-do list that removes all items where done is true. You need one line inside the click handler: todos = todos.filter(t => !t.done).
  2. Persist the to-do list across page reloads using localStorage. After every change, call localStorage.setItem("todos", JSON.stringify(todos)). On page load, read it back with JSON.parse(localStorage.getItem("todos") || "[]").
  3. Add keyboard support to the counter: when the user presses + or - on the keyboard, increment or decrement. Use document.addEventListener("keydown", handler) and check event.key.

Test Your Knowledge

Take a quick quiz on this lesson

Start Quiz →

Prefer watching over reading?

Subscribe for free.

Subscribe on YouTube