JavaScript From Zero · 20 min · 150 XP
Scope, closures and modules
Know where a variable can be seen, let a function remember state privately, and split code into modules that share only what they export.
Scope is where a name can be seen. let and const are block-scoped: a variable declared inside { } (an if, a loop, a function) exists only inside those braces. When JavaScript meets a name, it looks in the current block, then the block around it, and so on outwards to the top of the file. It never looks inwards, so an outer block can't see a variable declared inside an inner one.
const taxRate = 0.2; // visible everywhere below
function priceWithTax(price) {
const tax = price * taxRate; // taxRate found one level out
if (tax > 100) {
const note = "high tax"; // exists only inside this if
}
return price + tax; // note doesn't exist here
}
console.log(tax); // ReferenceError: tax is not definedKeep variables in the smallest scope that works. A variable at the top of a classic script is global: every other script on the page can read it, change it, or collide with it by using the same name. That's how two libraries that both define total break each other. var, which you'll meet in older code, ignores blocks and is scoped to the whole function, which is why it leaks out of loops.
A closure is what happens when a function uses a variable from the scope it was created in: the function keeps that variable alive, even after the outer function has returned. Each call to the outer function makes a fresh variable, so each closure has its own. It's the standard way to keep state private: nothing outside can reach count except through the functions you hand back.
function makeCounter() {
let count = 0; // a new count for every call
return {
increment: () => ++count,
value: () => count,
};
}
const likes = makeCounter();
const views = makeCounter();
likes.increment(); likes.increment();
views.increment();
likes.value(); // 2
views.value(); // 1
likes.count; // undefined: the variable isn't a property, and no one else can change itYou've already relied on closures: an event handler that changes a quantity variable declared outside it is one. React's hooks, later, are closures too, and one that captured an old value is behind a common kind of React bug.
Modules give every file its own scope. In a file loaded with type="module" (or imported by one), top-level variables are private to that file; nothing becomes global by accident. A file shares something by exporting it, and another file uses it by importing it by name. In the browser, the path needs the ./ and the .js. A module runs once, however many files import it, so they all share the same exported values.
// cart.js
const items = []; // private to this file
export function addItem(item) {
items.push(item);
}
export function itemCount() {
return items.length;
}
// app.js, loaded with <script type="module" src="./app.js">
import { addItem, itemCount } from "./cart.js";
addItem({ id: "tent" });
itemCount(); // 1
items; // ReferenceError: items is private to cart.jsThe lesson editor runs a single script, so it can't show import and export between files. The challenge practises scope and closures; to see modules work, make the two files above in a folder, serve it with your editor's local server, and open the page with the console open.
Loading your workspace…