Step 06 of 21
JavaScript basics
Work confidently with JavaScript values, variables, functions, objects, arrays and scope, including closures and common traps.
JavaScript is the only programming language browsers run natively, and it is what makes a page respond to people. Before you can change the page or talk to servers, you need a solid grip on the language itself: what kinds of values exist, how variables and functions work, how to model data with objects and arrays, and which names your code can see. This lesson covers that core, including the quirks that catch out newcomers.
Running JavaScript
You can run JavaScript in two places without installing anything special:
- The browser console. Open DevTools (F12) and choose the Console tab. Type an expression and press Enter.
- Node.js. Save code in a file such as
basics.jsand runnode basics.jsin a terminal.
console.log prints values, which is how you will check your work throughout this lesson:
const name = "Ada";
console.log("Hello,", name);
console.log(2 + 3 * 4);Hello, Ada
14Values and types
Every value in JavaScript has a type. There are seven primitive types, and everything else is an object:
| Type | Examples |
|---|---|
string | "hello", 'hi', `Total: ${n}` |
number | 42, 3.14, -0.5, NaN, Infinity |
bigint | 9007199254740993n |
boolean | true, false |
undefined | the value of a variable that has not been assigned |
null | an intentional "no value" |
symbol | Symbol("id"), a unique identifier |
object | { name: "Ada" }, [1, 2, 3], functions, dates |
The typeof operator tells you a value's type, with two famous quirks:
typeof "hi"; // "string"
typeof 42; // "number"
typeof undefined; // "undefined"
typeof (() => {}); // "function"
typeof [1, 2]; // "object" – use Array.isArray([1, 2]) instead
typeof null; // "object" – a bug from 1995 that can never be fixedNumbers
JavaScript has one number type for integers and decimals, stored as 64-bit floating point. That makes some decimal arithmetic inexact:
0.1 + 0.2; // 0.30000000000000004
(0.1 + 0.2).toFixed(2); // "0.30"
Number("42"); // 42
Number("42px"); // NaN ("not a number")
parseInt("42px", 10); // 42
Number.isNaN(NaN); // true – NaN is not even equal to itselfFor money, store whole amounts in the smallest unit (cents) and format only for display.
Strings
Strings can use single quotes, double quotes, or backticks. Backtick template literals can embed expressions with ${} and span several lines:
const item = "socks";
const qty = 3;
const msg = `You ordered ${qty} pairs of ${item}.`;
msg.length; // 29
msg.toUpperCase(); // "YOU ORDERED 3 PAIRS OF SOCKS."
msg.includes("socks"); // true
" padded ".trim(); // "padded"
"JavaScript".slice(0, 4); // "Java"Strings are immutable: methods return new strings rather than changing the original.
Variables: const and let
Declare variables with const by default and let when you need to reassign:
const taxRate = 0.2;
let total = 100;
total = total * (1 + taxRate); // fine: let can be reassigned
// taxRate = 0.25; // TypeError: Assignment to constant variable.const stops you reassigning the variable, not changing the value it holds. An object declared with const can still have its properties changed:
const user = { name: "Ada" };
user.name = "Grace"; // allowedYou will see var in older code. It ignores block scope and can be redeclared, which causes subtle bugs. Do not use it in new code.
Comparisons and truthiness
Use strict equality, === and !==. The loose == converts types before comparing, with surprising results:
0 == ""; // true
0 === ""; // false
null == undefined; // true
null === undefined; // false
"5" + 3; // "53" – + with a string concatenates
"5" - 3; // 2 – - converts to numbersIn an if or a && / || expression, every value is treated as truthy or falsy. There are only a few falsy values: false, 0, -0, 0n, "", null, undefined and NaN. Everything else is truthy – including "0", "false", [] and {}.
Two operators handle missing values safely:
const settings = { volume: 0, theme: null };
settings.volume || 50; // 50 – wrong! || treats 0 as missing
settings.volume ?? 50; // 0 – ?? only replaces null and undefined
settings.theme ?? "light"; // "light"
const order = { customer: null };
order.customer?.address?.city; // undefined, instead of a TypeError?. (optional chaining) stops and returns undefined if the value on its left is null or undefined. ?? (nullish coalescing) provides a default only for null and undefined.
Functions
A function is a reusable block of code that takes inputs (parameters) and can return an output. There are three common ways to write one:
// Function declaration
function add(a, b) {
return a + b;
}
// Function expression stored in a variable
const subtract = function (a, b) {
return a - b;
};
// Arrow function: concise; an expression body is returned automatically
const multiply = (a, b) => a * b;
const square = (n) => n * n;
const makeUser = (name) => ({ name, active: true }); // wrap object literals in ()Things to know:
- A function without a
returnstatement returnsundefined. - Parameters can have defaults:
function greet(name = "friend") { … }. - Function declarations are hoisted: you can call them above the line where they are defined. Function expressions and arrows cannot be used before their line.
- Functions are values. You can store them, pass them to other functions, and return them. A function passed in to be called later is a callback:
function applyDiscount(prices, rule) {
return prices.map(rule);
}
applyDiscount([100, 40], (p) => p * 0.9); // [90, 36]Objects and arrays
Objects
An object groups related values under named properties:
const product = {
name: "Ridge Runner",
price: 120,
tags: ["trail", "waterproof"],
describe() {
return `${this.name} costs £${this.price}`;
},
};
product.price; // 120
product["name"]; // "Ridge Runner" – brackets allow computed keys
product.colour = "grey"; // add a property
delete product.colour; // remove it
product.describe(); // "Ridge Runner costs £120"Destructuring pulls properties into variables, and spread copies properties into a new object:
const { name, price } = product;
const discounted = { ...product, price: 99 }; // new object; product is unchangedTo loop over an object's contents, turn it into an array first. Object.keys gives the property names, Object.values the values, and Object.entries gives [key, value] pairs, which pair nicely with destructuring:
const stock = { socks: 12, caps: 0, jackets: 3 };
Object.keys(stock); // ["socks", "caps", "jackets"]
Object.values(stock); // [12, 0, 3]
for (const [item, count] of Object.entries(stock)) {
console.log(`${item}: ${count}`);
}An object used this way – arbitrary keys mapping to values – is often called a dictionary or lookup table. It is one of the most common data structures you will build.
Arrays
Arrays are ordered lists, indexed from 0:
const sizes = [40, 41, 42];
sizes[0]; // 40
sizes.length; // 3
sizes.push(43); // adds to the end, returns the new length (4)
sizes.includes(42); // true
for (const size of sizes) {
console.log(size);
}The array methods you will use constantly take a callback, call it once per element, and return something new without changing the original array. map transforms every element, filter keeps the ones that pass a test, find returns the first match (or undefined), some asks whether any element passes, and reduce folds the whole array into a single value, starting from the initial value you give it:
const products = [
{ name: "Socks", price: 8, inStock: true },
{ name: "Jacket", price: 150, inStock: false },
{ name: "Cap", price: 20, inStock: true },
];
products.map((p) => p.name); // ["Socks", "Jacket", "Cap"]
products.filter((p) => p.inStock); // Socks and Cap objects
products.find((p) => p.price > 100); // the Jacket object
products.some((p) => !p.inStock); // true
products.reduce((sum, p) => sum + p.price, 0); // 178Values versus references
Primitives are copied by value. Objects and arrays are held by reference: assigning one to another variable copies the reference, not the data.
const a = { count: 1 };
const b = a; // the same object
b.count = 2;
a.count; // 2
const c = { ...a }; // shallow copy: a new top-level object
const d = structuredClone(a); // deep copy, including nested objectsScope and closures
Scope decides which variables a piece of code can see. Variables declared with let and const are block-scoped: they exist only inside the nearest { … }.
const greeting = "hi"; // outer scope
function shout() {
const loud = greeting.toUpperCase(); // inner code can read outer variables
return loud;
}
if (true) {
const temp = 1;
}
// console.log(temp); // ReferenceError: temp is not definedJavaScript uses lexical scope: what a function can see is determined by where it is written, not where it is called. Accessing a let or const before its declaration line throws a ReferenceError; that region is called the temporal dead zone.
A closure is a function that remembers the variables from the scope where it was created, even after that outer function has returned:
function makeCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const next = makeCounter();
next(); // 1
next(); // 2
const other = makeCounter();
other(); // 1 – each call to makeCounter creates a fresh countcount is private: nothing outside can change it except through next. Closures are how event handlers, timers and callbacks remember the data they need.
Practice
Save this in orders.js and implement the four functions below it. Run it with node orders.js.
const orders = [
{ id: 1, customer: "ana", items: [{ price: 12, qty: 2 }, { price: 5, qty: 1 }], status: "paid" },
{ id: 2, customer: "ben", items: [{ price: 40, qty: 1 }], status: "refunded" },
{ id: 3, customer: "ana", items: [{ price: 8, qty: 3 }], status: "paid" },
{ id: 4, customer: "cleo", items: [], status: "pending" },
];orderTotal(order)returns the sum ofprice * qtyfor its items.paidRevenue(orders)returns the total of all orders with status"paid".totalsByCustomer(orders)returns an object like{ ana: 53, ben: 40, cleo: 0 }(every status counts).makeIdGenerator(start)returns a function that yieldsstart, thenstart + 1, and so on.
Show solution
function orderTotal(order) {
return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
function paidRevenue(orders) {
return orders
.filter((order) => order.status === "paid")
.reduce((sum, order) => sum + orderTotal(order), 0);
}
function totalsByCustomer(orders) {
const totals = {};
for (const order of orders) {
totals[order.customer] = (totals[order.customer] ?? 0) + orderTotal(order);
}
return totals;
}
function makeIdGenerator(start) {
let next = start;
return () => next++;
}
console.log(orderTotal(orders[0]));
console.log(paidRevenue(orders));
console.log(totalsByCustomer(orders));
const nextId = makeIdGenerator(100);
console.log(nextId(), nextId(), nextId());29
53
{ ana: 53, ben: 40, cleo: 0 }
100 101 102The initial value 0 in reduce is essential: it makes an empty items array total 0 instead of throwing. ?? is used rather than || so the default only applies when the customer has no entry yet. next++ returns the current value and then increments it, and the closure keeps next alive between calls.
Key takeaways
- Seven primitive types plus objects;
typeof nullis"object", and arrays are checked withArray.isArray. - Use
constby default,letfor reassignment, and nevervar;constobjects can still be mutated. - Compare with
===; know the falsy values, and use??and?.to handle missing data. - Functions are values: pass them as callbacks and use
map,filter,findandreduceto transform arrays. - Objects and arrays are shared by reference; copy with spread or
structuredClone. - Scope is lexical and block-based, and closures let functions keep private state.
Check yourself
Quiz
0/4 correct · 4 left
1.What does console.log(settings.volume || 50) print when settings.volume is 0?
2.After const a = [1, 2]; const b = a; b.push(3); what is a?
3.What is the value of [3, 20, 100].sort()?
4.Using makeCounter from this lesson, you run const f = makeCounter(); f(); f(); const g = makeCounter(); What does the first call to g() return?
Finished reading?
Mark it complete to fill in your roadmap.