A practical JavaScript guide covering modern syntax, functions, arrays and objects, the DOM, events, promises, async/await, fetch, modules, classes, storage, and best practices.
What Is JavaScript?
JavaScript is a programming language that runs in browsers and, through runtimes such as Node.js, on servers. In a web page it can respond to clicks, update the DOM, validate input, and request data from APIs.
Where JavaScript runs
- Browsers: interactive pages, animations, and single-page apps.
- Node.js, Deno, Bun: servers, command-line tools, and build scripts.
- Mobile and desktop: frameworks such as React Native and Electron.
Adding JavaScript to a page
- Use
<script src="app.js" defer></script>so the script runs after the HTML is parsed. - Use
type="module"to enableimportandexport. - Open the browser console (F12) to test code and read errors.
Your first script
// app.js
console.log("Hello, JavaScript!");
const heading = document.querySelector("h1");
heading.textContent = "Hello from JavaScript";Variables and Data Types
- Use
constby default for values that are not reassigned. - Use
letwhen a variable must change. - Avoid
var; it has confusing function scope and hoisting behavior. - Primitive types:
string,number,bigint,boolean,undefined,null,symbol. - Everything else is an object, including arrays, functions, and dates.
- Use strict equality (
===) unless coercion is intentional.
Variables, types, and template strings
const name = "Asha";
let score = 10;
score += 5;
const isActive = true;
const nothing = null;
let notSet; // undefined
console.log(typeof name); // "string"
console.log(typeof score); // "number"
console.log(0.1 + 0.2 === 0.3); // false (floating-point math)
console.log(`Player ${name} has ${score} points`);
console.log(1 == "1"); // true (coercion)
console.log(1 === "1"); // false (preferred)Truthy and falsy values
- Falsy values:
false,0,"",null,undefined,NaN. - Everything else is truthy, including
"0",[], and{}. - Use
??to default onlynull/undefined, and?.for safe property access.
Optional chaining and nullish coalescing
const user = { profile: { city: "Delhi" } };
console.log(user.profile?.city); // "Delhi"
console.log(user.address?.street); // undefined (no error)
const count = 0;
console.log(count || 10); // 10 (0 is falsy)
console.log(count ?? 10); // 0 (only null/undefined replaced)Functions, Scope, and Closures
- Function declarations are hoisted and can be called before they appear.
- Arrow functions are concise and keep the surrounding
this. - **Default parameters, rest (
...args), and spread (...arr)** make functions flexible. - Scope: variables declared with
letandconstare block scoped. - Closures: a function remembers the variables from where it was created.
Function styles, defaults, and a closure
function add(a, b) {
return a + b;
}
const multiply = (a, b) => a * b;
const greet = (name = "friend") => `Hello, ${name}!`;
const sum = (...numbers) => numbers.reduce((total, n) => total + n, 0);
function makeCounter() {
let count = 0; // private, kept alive by the closure
return () => ++count;
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(sum(1, 2, 3, 4)); // 10Arrays, Objects, and Destructuring
Most real-world JavaScript is about transforming collections of data. Array methods describe each operation clearly.
maptransforms every item into a new array.filterkeeps items that pass a test.findreturns the first matching item.reducecombines items into a single value.someandeverytest whether any or all items match.sortsorts in place; copy first with[...arr]ortoSorted()to avoid mutation.
Transforming data with array methods
const products = [
{ id: 1, name: "Keyboard", price: 49, inStock: true },
{ id: 2, name: "Mouse", price: 25, inStock: false },
{ id: 3, name: "Monitor", price: 199, inStock: true },
];
const names = products.map((p) => p.name);
const available = products.filter((p) => p.inStock);
const monitor = products.find((p) => p.name === "Monitor");
const total = available.reduce((sum, p) => sum + p.price, 0);
console.log(names); // ["Keyboard", "Mouse", "Monitor"]
console.log(total); // 248Destructuring, spread, and object shorthand
const { name, price } = products[0];
const [first, second, ...rest] = products;
const updated = { ...products[0], price: 45 }; // copy with change
const merged = [...products, { id: 4, name: "Webcam", price: 59, inStock: true }];
const entries = Object.entries({ a: 1, b: 2 }); // [["a", 1], ["b", 2]]Control Flow and Loops
if/else if/elsefor decisions andswitchfor many fixed cases.for...ofiterates values of arrays, strings, and other iterables.for...initerates object keys; preferObject.keysorObject.entries.whileanddo...whilerepeat until a condition changes.breakexits a loop andcontinueskips to the next iteration.
Loops and conditions
const scores = [92, 74, 58, 85];
for (const score of scores) {
if (score >= 90) console.log(score, "A");
else if (score >= 70) console.log(score, "B");
else console.log(score, "Needs work");
}
let attempts = 0;
while (attempts < 3) {
console.log("Attempt", ++attempts);
}Working with the DOM
The DOM represents the page as objects you can read and change from JavaScript.
- Select:
querySelector,querySelectorAll,getElementById. - Change text:
textContentis safe for plain text. - Change classes:
classList.add,remove,toggle. - Create elements:
createElement, thenappend. - Attributes:
setAttribute,dataset, and properties such ashidden. - Avoid
innerHTMLwith user input because it can lead to cross-site scripting (XSS).
Creating and updating elements safely
const list = document.querySelector("#todo-list");
function addItem(text) {
const li = document.createElement("li");
li.textContent = text; // safe: treated as text, not HTML
li.classList.add("todo-item");
li.dataset.created = Date.now();
list.append(li);
}
addItem("Learn the DOM");
addItem("Build a small project");
document.body.classList.toggle("dark");Events and Event Delegation
Events let your code react to clicks, typing, form submission, and more. Event delegation attaches one listener to a parent instead of many to children.
Listening for events and delegating clicks
const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-input");
const list = document.querySelector("#todo-list");
form.addEventListener("submit", (event) => {
event.preventDefault(); // stop page reload
const text = input.value.trim();
if (!text) return;
addItem(text);
input.value = "";
input.focus();
});
// One listener handles clicks on every current and future item
list.addEventListener("click", (event) => {
const item = event.target.closest("li");
if (item) item.classList.toggle("done");
});Asynchronous JavaScript: Promises and async/await
Network requests, timers, and file access take time. JavaScript handles them asynchronously so the page stays responsive.
The evolution of async code
- Callbacks: functions passed to run later; deeply nested callbacks become hard to read.
- Promises: objects representing a future result with
.then(),.catch(),.finally(). - async/await: syntax that makes promise code read top to bottom.
Promises and async/await compared
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Promise chain
wait(500)
.then(() => console.log("Half a second later"))
.catch((err) => console.error(err));
// async/await version
async function run() {
await wait(500);
console.log("Half a second later");
}
// Run tasks in parallel
async function loadBoth() {
const [a, b] = await Promise.all([fetchUser(1), fetchUser(2)]);
return [a, b];
}Calling APIs with fetch and Handling Errors
fetchonly rejects on network failure; HTTP errors like 404 and 500 still resolve.- Always check
response.okbefore reading the body. - Wrap awaited calls in
try/catchand show a helpful message to the user. - Use
AbortControllerto cancel requests that are no longer needed. - Never put secret API keys in browser code.
A safe fetch helper with loading and error handling
async function getJson(url, { signal } = {}) {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}
return response.json();
}
async function showUsers() {
const status = document.querySelector("#status");
const list = document.querySelector("#users");
status.textContent = "Loading...";
try {
const users = await getJson("https://jsonplaceholder.typicode.com/users");
list.replaceChildren(
...users.slice(0, 5).map((user) => {
const li = document.createElement("li");
li.textContent = `${user.name} (${user.email})`;
return li;
})
);
status.textContent = "";
} catch (error) {
status.textContent = "Could not load users. Please try again.";
console.error(error);
}
}Modules and Classes
- Modules split code into files with
exportandimport. - Named exports can be many per file; a default export is one main value.
- Classes bundle data and behavior with a constructor, methods, and optional private fields (
#field). - Prefer small functions and plain objects; use classes when you need many instances with shared behavior.
A module that exports a class
// cart.js
export class Cart {
#items = []; // private field
add(product, quantity = 1) {
this.#items.push({ product, quantity });
return this;
}
get total() {
return this.#items.reduce((sum, i) => sum + i.product.price * i.quantity, 0);
}
}
export const formatPrice = (n) => `$${n.toFixed(2)}`;
// main.js (loaded with <script type="module" src="main.js">)
import { Cart, formatPrice } from "./cart.js";
const cart = new Cart().add({ name: "Mouse", price: 25 }, 2);
console.log(formatPrice(cart.total)); // $50.00Saving Data in the Browser
localStoragekeeps strings across visits;sessionStoragelasts for one tab session.- Use
JSON.stringifyandJSON.parseto store objects and arrays. - Wrap parsing in
try/catchbecause stored data can be missing or invalid. - Never store passwords or sensitive tokens in
localStorage.
Persisting a to-do list
const KEY = "todos";
function loadTodos() {
try {
return JSON.parse(localStorage.getItem(KEY)) ?? [];
} catch {
return [];
}
}
function saveTodos(todos) {
localStorage.setItem(KEY, JSON.stringify(todos));
}
const todos = loadTodos();
todos.push({ id: crypto.randomUUID(), text: "Practice JavaScript", done: false });
saveTodos(todos);Best Practices and Common Mistakes
- Use
constandlet; never rely on accidental globals. - Prefer
===and handlenull/undefinedexplicitly. - Keep functions small and name them after what they do.
- Handle errors in every async path and give users feedback.
- Do not trust user input; validate on the server and avoid
innerHTML/eval. - Debounce expensive handlers such as search-as-you-type.
- Use ESLint and Prettier to catch mistakes and keep style consistent.
- Learn browser DevTools: Console, Network, and the debugger.
Complete Example: A Persistent To-Do App
Save this as an HTML file and open it in a browser. It uses the DOM, events, delegation, localStorage, and safe text rendering.
A complete to-do app in one file
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>To-Do App</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 32rem; margin: 2rem auto; padding: 0 1rem; }
li.done span { text-decoration: line-through; opacity: 0.6; }
li { display: flex; gap: 0.5rem; align-items: center; margin: 0.4rem 0; }
</style>
</head>
<body>
<h1>My To-Do List</h1>
<form id="form">
<label for="input">New task</label>
<input id="input" required>
<button>Add</button>
</form>
<ul id="list"></ul>
<script>
const KEY = "todos";
const form = document.querySelector("#form");
const input = document.querySelector("#input");
const list = document.querySelector("#list");
let todos = load();
render();
function load() {
try { return JSON.parse(localStorage.getItem(KEY)) ?? []; }
catch { return []; }
}
function save() { localStorage.setItem(KEY, JSON.stringify(todos)); }
function render() {
list.replaceChildren(
...todos.map((todo) => {
const li = document.createElement("li");
li.dataset.id = todo.id;
li.classList.toggle("done", todo.done);
const box = document.createElement("input");
box.type = "checkbox";
box.checked = todo.done;
const text = document.createElement("span");
text.textContent = todo.text;
const del = document.createElement("button");
del.textContent = "Delete";
del.dataset.action = "delete";
li.append(box, text, del);
return li;
})
);
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
todos.push({ id: crypto.randomUUID(), text, done: false });
save(); render();
form.reset(); input.focus();
});
list.addEventListener("click", (e) => {
const li = e.target.closest("li");
if (!li) return;
const id = li.dataset.id;
if (e.target.dataset.action === "delete") {
todos = todos.filter((t) => t.id !== id);
} else if (e.target.type === "checkbox") {
todos = todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t));
} else return;
save(); render();
});
</script>
</body>
</html>Frequently Asked Questions
Is JavaScript the same as Java?
No. They are separate languages with different ecosystems and language designs, despite the similar names.
What is async/await?
It is syntax for working with promises in a sequential-looking style. Errors can be handled with try/catch.
Do I need a framework to learn JavaScript?
No. Learn language fundamentals and browser APIs first; frameworks such as React or Vue become much easier afterward.
What is the difference between let, const, and var?
const declares a binding that is not reassigned, let declares a reassignable block-scoped variable, and var is older function-scoped syntax that is best avoided.
Why does fetch not throw on a 404 error?
fetch only rejects for network-level failures. HTTP error statuses still resolve, so you must check response.ok and throw your own error.
What is the DOM?
The Document Object Model is the browser's tree of objects representing the page. JavaScript uses it to read and change content, styles, and structure.