- Learn
- Computer Science & IT
- Programming
- JavaScript Programming
- JavaScript Classes and Modules
JavaScript Classes and Modules: Developer Tools and npm
package.json, and use the everyday tools of a modern workflow: a linter and formatter, a dev server and bundler, and a test runner. Finish by seeing where TypeScript and front-end frameworks fit into that toolchain.BeginnerLesson 9 of 9 in JavaScript Programming Course
What you will learn
- Use classes to create objects with shared behavior, protect their data, build hierarchies with inheritance, and explain the prototype chain that classes are built on.
- Define a class and create objects with newWrite a class with a constructor, fields, methods, getters and setters, create instances with
new, and keepthispointing at the right object. - Protect data with private members and share values with static membersHide an object's internal state behind private fields and methods, and put values and helpers that belong to the class as a whole into static members.
- Build class hierarchies with extends and superCreate subclasses that inherit and override behavior, call the parent with
super, check types withinstanceof, and decide when inheritance or composition fits better. - Explain the prototypes behind objects and classesDescribe how JavaScript finds properties along the prototype chain, how classes and
extendsset up that chain, and why built-in prototypes are left unchanged.
- Define a class and create objects with new
- Split a program into modules that export and import features, run them in browsers and Node.js, load code on demand and give each module one clear job.
- Export and import code between filesSplit a program into ES modules and connect them with named and default exports, renamed and namespace imports, and re-exports, knowing how module scope and live bindings behave.
- Run modules in the browser and in Node.jsLoad modules in a web page with
type="module"from a local server, resolve module paths and bare names, and understand how Node.js chooses between ES modules and CommonJS. - Load modules on demand and organize a project into modulesUse dynamic
import()and top-levelawait, split an application into focused modules, and avoid problems with side effects and circular imports.
- Export and import code between files
- Describe a project in
package.json, run its tasks with npm scripts, and install, update and choose dependencies so that every computer gets the same working set of packages.- Set up a project with npm and package.jsonCreate a project with
npm init, read and edit the mainpackage.jsonfields, run tasks with npm scripts andnpx, and read semantic version numbers. - Install, update and choose dependenciesInstall packages as runtime or development dependencies, read version ranges, use
package-lock.jsonandnpm cifor repeatable installs, keep dependencies updated and choose packages safely.
- Set up a project with npm and package.json
- Use a linter and a formatter to catch problems and keep a consistent style, a dev server and bundler to run and ship a project, and a test runner to check that the code works.
- Catch problems with a linter and format code automaticallyUse ESLint to find likely bugs without running the code and Prettier to keep a consistent layout, configure both, and run them from scripts and the editor.
- Serve and bundle a project with a dev server and a bundlerExplain what bundlers and dev servers do, run the develop–build–preview cycle with Vite, and understand tree shaking, minification, hashed file names, source maps and environment variables.
- Check code automatically with a test runnerWrite unit tests with assertions for functions and classes, run them with Node.js's built-in test runner or Vitest, read failures and design code that is easy to test.
- Catch problems with a linter and format code automatically
- Explain what TypeScript and front-end frameworks add on top of JavaScript, how they use modules, npm and build tools, and how all the tools work together in a project.
- Explain what TypeScript adds to JavaScriptDescribe how TypeScript's static types catch mistakes before code runs, how TypeScript code becomes JavaScript, and when adopting it is worth the extra step.
- Place frameworks in a complete modern toolchainExplain what front-end frameworks do and how they build on modules, npm and bundlers, and assemble the tools of this unit into one everyday workflow.
- Explain what TypeScript adds to JavaScript
JavaScript Classes and Modules study guide
1Build objects that share behavior
A plain object groups related values. A class adds a repeatable way to create objects with their own data and shared behavior. Start with constructors and methods, then control changes through private fields and a public interface. Inheritance lets related classes specialize that behavior. The prototype chain explains how JavaScript finds the methods and why an instance can use code stored somewhere else.
1.1 Create and use class instances
A class describes objects with the same structure and behavior. Each object created from it is an instance. Use a class when several objects need the same operations; a plain object is often enough for one settings value.
Give each object its own data
Class names conventionally use PascalCase, such as Rectangle. The constructor runs each time new creates an instance. Inside it, this refers to the new object. Assigning to this.width stores an instance property; a parameter named width alone is only a local variable.
class Rectangle {
constructor(width, height) {
this.width = width; // cm
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
const a = new Rectangle(3, 4);
const b = new Rectangle(5, 2);
a.area(); // 12
a.perimeter(); // 14
b.area(); // 10
For a.area(), JavaScript calls the method with this set to a. Multiplying its width, 3 cm, by its height, 4 cm, gives 12 cm². The perimeter adds the two different sides and doubles that sum, giving 14 cm. Changing b.width changes only b.
Methods have no function keyword and no commas between them. A class has at most one constructor. For a class without extends, omitting it gives an empty default constructor. new returns the created instance automatically.
A field declares a property in the class body: unit = "cm"; gives each instance its own value. label; starts as undefined. In a class without extends, instance fields are initialized before the constructor body runs.
Read and assign through accessors
A getter runs when a property is read. With get area() { return this.width * this.height; }, read a.area without parentheses. This is an alternative to the method above, which uses a.area().
A setter runs on assignment and can reject invalid input. It needs separate storage: set width(value) { this.width = value; } calls itself repeatedly until the stack overflows. Store the value under a different name, such as the private field #width.
Keep the receiver when passing a method
class Counter {
count = 0;
increment() { this.count++; }
}
const counter = new Counter();
const inc = counter.increment;
// inc(); // TypeError: this is undefined
const safeInc = () => counter.increment();
safeInc(); // counter.count is 1
Class methods run in strict mode. A detached call has no receiver before the dot, so this is undefined. A callback's caller can also supply a different receiver. An arrow wrapper preserves the call on counter; counter.increment.bind(counter) creates a bound function. An arrow-function field, increment = () => { this.count++; };, captures the instance's this too.
Calling Rectangle(3, 4) without new throws a TypeError. Using a class before its declaration has initialized throws a ReferenceError. Classes differ here from function declarations that can be called earlier in a file.
JSON preserves data, not class behavior. JSON.stringify(a) saves its own public width and height. Parsing that text creates a plain object; rebuild an instance with new Rectangle(data.width, data.height) to use its methods.
Key Takeaways
newcreates an instance and runs its constructor;thisidentifies the receiver of a method call.- Fields hold each instance's data; ordinary methods are shared.
- Getters are read as properties; setters run on assignment and need separate storage.
- Use an arrow wrapper or a bound function when a callback must keep its receiver.
- JSON does not restore class methods when it parses saved data.
1.2 Guard state with private fields
An object can offer a small public interface while keeping its storage private. This is encapsulation. The interface decides which changes are valid; privacy alone does not check a value passed to a public setter.
Put validation at the boundary
A private field starts with # and must be declared in the class body. Code inside that class reads it through this.#target. Code outside cannot read or write t.#target: that is a syntax error, not a property read that returns undefined.
class Thermostat {
static MIN = 10;
static MAX = 30;
#target = 20;
get target() { return this.#target; }
set target(value) {
if (!Number.isFinite(value) ||
value < Thermostat.MIN ||
value > Thermostat.MAX) {
throw new RangeError("Target must be between 10 and 30 °C");
}
this.#target = value;
}
}
const t = new Thermostat();
t.target = 22;
t.target; // 22
// t.target = 35; // throws RangeError
The getter exposes a reading without exposing storage. The setter first rejects nonfinite or nonnumeric input, then values outside the inclusive 10–30 °C range. Only an accepted value reaches the assignment. After a rejected attempt to set 35 °C, the target remains 22 °C. A numeric string such as "22" also fails Number.isFinite.
The getter and setter use #target, so assigning t.target does not call the setter recursively. The names target and #target are distinct. A leading underscore, as in _target, only asks other programmers to treat a public property as internal; JavaScript still permits access to it.
Private fields do not appear in Object.keys, for...in, or JSON serialization. This thermostat has no own public fields, so JSON.stringify(t) produces "{}". Saving an object may therefore need an explicit public method that returns the data to save.
Keep internal helpers private
A method such as #check(value) { ... } can hold validation steps and is called inside the class as this.#check(value). It is not part of the public interface. Inside the class, #target in obj tests whether an object carries that private field.
A subclass cannot use its parent's private name directly. It can use the parent's public getter or setter, which runs in the class that declared the field. This lets the parent retain control of its own state.
Put shared work on the class
A static member belongs to the class. Thermostat.MIN is 10, while t.MIN is undefined. These static limits are public fields; static does not itself make them read-only.
class Tracked {
static count = 0;
constructor() { Tracked.count++; }
}
new Tracked();
new Tracked();
new Tracked();
Tracked.count; // 3
Each constructor increments one class-level counter. An instance field would instead give each object a separate count. A static factory, such as static square(side) { return new Rectangle(side, side); }, belongs inside Rectangle and is called as Rectangle.square(5). A static comparison helper can be passed to sort when it takes the compared objects as arguments.
Inside a static method, this is the class it was called on; it is not a rectangle instance with a width. A static { ... } initialization block performs setup once when the class is defined and can access that class's private members.
Key Takeaways
- Private
#fields are enforced by JavaScript; an underscore is only a convention. - Validate through the public interface before changing private state.
- Private fields are absent from ordinary key enumeration and JSON serialization.
- Subclasses use the parent's public interface to work with its private state.
- Call static members on the class; they do not hold each instance's data.
- A static initialization block runs once when its class is defined.
1.3 Specialize a class with inheritance
Inheritance models an is a relationship. A circle is a shape, so a Circle class can inherit a shape's shared description while supplying its own area calculation. A subclass inherits behavior without copying every method into its body.
Initialize the parent first
class Circle extends Shape makes Circle a subclass and Shape its superclass. In a subclass constructor, super(...) calls the parent's constructor. It must run before the first use of this. A subclass with no constructor gets one that forwards its arguments to the parent.
class Shape {
constructor(name) { this.name = name; }
area() { return 0; }
describe() {
return this.name + " with area " +
this.area().toFixed(2) + " cm²";
}
}
class Circle extends Shape {
constructor(radius) {
super("Circle");
this.radius = radius;
}
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(width, height) {
super("Rectangle");
this.width = width;
this.height = height;
}
area() { return this.width * this.height; }
}
Calling new Circle(2).describe() gives "Circle with area 12.57 cm²". First, super sets the name. The circle stores its radius. The inherited describe() then calls this.area(), which finds the circle's method. toFixed(2) formats the result for display. new Rectangle(3, 4).describe() gives "Rectangle with area 12.00 cm²".
Override or extend a method
A subclass overrides a method by declaring the same name. It can call the parent's version through super.methodName().
class Square extends Rectangle {
constructor(side) {
super(side, side);
this.name = "Square";
}
describe() {
return super.describe() +
" (all sides " + this.width + " cm)";
}
}
new Square(5).describe();
// "Square with area 25.00 cm² (all sides 5 cm)"
super(side, side) initializes equal width and height. The inherited area method multiplies them, and the override adds information to the inherited description. Forgetting required parent arguments can leave its properties undefined; calling super() is not enough if the parent needs values.
Polymorphism means the same call can select different behavior. A loop can call s.describe() on circles and rectangles; each description calls the appropriate area(). Adding a shape with its own area method does not require a new case in that loop.
new Circle(2) instanceof Circle and new Circle(2) instanceof Shape are both true. Static members are inherited too. An instance's relationship to a class follows its prototype chain, which is what instanceof normally checks.
Choose a useful relationship
A class can extend only one parent. For a has a relationship, use composition: a car has an engine and asks this.engine.start() to do work. Sharing a helper alone is not a reason to claim one unrelated class is a kind of another. A mixin copies a group of methods onto a prototype, for example with Object.assign(Rectangle.prototype, serializable). Deep parent chains can make changes difficult to follow.
Built-in classes can also be extended. A ValidationError extends Error constructor calls super(message), sets this.name = "ValidationError", and can store this.field = field. A catch block can use err instanceof ValidationError to handle that failure differently and read the inherited message.
Key Takeaways
- Use
extendsfor an is-a relationship and composition for a has-a relationship. - Call
super(...)with the needed parent arguments before usingthis. - An override can reuse parent behavior with
super.methodName(). - Polymorphism lets one method call work across related classes.
1.4 Follow property lookup through prototypes
An object has a hidden link to another object, its prototype, or to null. Reading a missing property follows that link. JavaScript keeps searching until it finds the property or reaches null; an unsuccessful search returns undefined.
Distinguish stored and inherited values
const defaults = { theme: "light", fontSize: 16 };
const settings = Object.create(defaults);
settings.fontSize = 18;
settings.theme; // "light"
settings.fontSize; // 18
defaults.fontSize; // 16
Object.hasOwn(settings, "theme"); // false
"theme" in settings; // true
Object.keys(settings); // ["fontSize"]
Object.create(defaults) creates an empty object whose prototype is defaults. Assigning fontSize creates an own property that shadows the inherited value. It leaves defaults.fontSize unchanged. After delete settings.fontSize, lookup reaches the inherited 16 again.
| Check | Properties it considers |
|---|---|
Object.hasOwn(obj, key) | Own properties, enumerable or not |
Object.keys(obj) | Own enumerable string keys |
key in obj | Own and inherited properties, enumerable or not |
for...in | Enumerable string keys, including inherited ones |
A missing own property may still exist on the chain. Class methods are not enumerable, so for...in does not normally list them. Use Object.getPrototypeOf(settings) to read its prototype. Prefer these Object methods to the older __proto__ accessor.
See how classes share methods
For a rectangle instance r, Object.getPrototypeOf(r) === Rectangle.prototype is true. Its width is an own property, while area is normally stored on Rectangle.prototype. Object.hasOwn(r, "area") is false even though r.area() works. An arrow-function field is different: it creates an own function property on each instance.
An inherited method still receives the object before the dot as this. The shared area method can therefore read different dimensions for each rectangle without storing a separate method on each object.
For the shape hierarchy, lookup follows:
circle -> Circle.prototype -> Shape.prototype
-> Object.prototype -> null
The circle's area method is found first. Its description comes from Shape.prototype, and toString can come from Object.prototype. Shape.prototype in the chain also explains why circle instanceof Shape is true.
The name Rectangle.prototype means the prototype given to rectangle instances. It does not name the hidden prototype of the Rectangle function itself; for this class that is Shape when it extends Shape, or Function.prototype for a class without a superclass.
Recognize older constructor code
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.area = function () {
return this.width * this.height;
};
Constructor functions use the same basic instance-to-prototype structure. Class syntax adds rules: new is required, the body is strict, methods are nonenumerable, and private members are available. typeof Rectangle is "function" for either form.
Arrays find map on Array.prototype; string methods come from String.prototype. Avoid adding custom methods to built-in prototypes: libraries and future standards may use the same names with different behavior. The existing library use of flatten helped lead to the standard array method being named flat.
Ordinary data-property assignment creates or changes an own property, but an inherited setter can intercept the assignment. Changing an existing object's prototype with Object.setPrototypeOf is legal but can hurt performance; choose the prototype when creating the object.
Key Takeaways
- Property reads search the object, its prototypes, and finally
null. - An own property can shadow an inherited value without changing the prototype.
- Ownership and enumerability are different questions.
- Ordinary class methods live on the instance prototype and share behavior.
- An inherited method uses the receiving instance as
this. - Constructor functions and classes build on the same prototype mechanism.
- Leave built-in prototypes unchanged to avoid name and behavior conflicts.
2Connect files with ES modules
Modules let a program grow beyond one script while keeping each file understandable. Exports describe what a file offers, and imports show what it needs. Learn how that connection works, how browsers and Node.js load it, and how to load a feature only when it is requested. Clear boundaries between data, storage, and page updates also make code easier to test and help prevent circular dependencies.
2.1 Export features and import their bindings
An ES module has its own scope. Its top-level declarations stay inside the file unless exported. Importers request specific features, making dependencies visible without putting everything in global variables.
Match the export form
// geometry.js
export const round2 = (n) => Math.round(n * 100) / 100;
export function circleArea(r) {
return Math.PI * r * r;
}
// shapes.js
import { circleArea, round2 } from "./geometry.js";
export default class Circle {
constructor(r) { this.r = r; }
area() { return round2(circleArea(this.r)); }
}
// main.js
import Circle from "./shapes.js";
console.log(new Circle(3).area()); // 28.27
The entry module imports one class. The class's module imports two helpers. For a radius of 3 cm, the area is about 28.274 cm²; the rounding helper produces 28.27. The entry module does not automatically gain access to the helpers used inside another module.
| Export | Corresponding import |
|---|---|
export const MAX = 30; | import { MAX } from "./limits.js"; |
export default class Circle {} | import Circle from "./shapes.js"; |
A default plus named round2 | import Circle, { round2 } from "./shapes.js"; |
A module may have many named exports but at most one default export. Named imports use the exported name in braces; the local name for a default import is chosen by the importer. Writing import { Circle } does not request the default export even when the exported class is named Circle.
Rename, group, and re-export
import { circleArea as areaOfCircle } from "./geometry.js"; gives an imported feature a different local name. export { circleArea as area }; changes its exported name. An export list can also expose declarations made earlier in the file.
import * as geometry from "./geometry.js"; creates a module namespace object. Call geometry.circleArea(3). A namespace includes a default export under the property default if the module has one.
A file can collect other files' exports: export { circleArea } from "./geometry.js"; forwards a named export. export * from "./geometry.js"; forwards named exports, but does not forward that module's default export.
Understand shared evaluation and live bindings
Modules run in strict mode and have undefined as top-level this. Dependencies are evaluated before the importing module's own body. Importing the same resolved module from several files reuses one evaluated module and its state.
Suppose a module exports let count = 0 and increment() changes it. After an importer calls increment(), its imported count reads 1. Imports are live bindings, not snapshots. The importer cannot reassign the binding itself. If an export is an object, changing one of that object's properties is still possible and other importers see it.
Keep static import declarations at module top level. A static import inside an if or function is a syntax error; dynamic import() handles later loading. Include .js in relative browser and Node.js ES module paths: these loaders do not add the extension. A path error or a named/default mismatch prevents the module from loading, so fix the import before looking for a bug in the code that was meant to run afterward.
Key Takeaways
- Modules keep top-level declarations local unless they are exported.
- Named imports use braces; a default import uses no braces.
- A namespace groups exports, while re-exports forward a module's interface.
- Imports are live read-only bindings to shared module state.
- Static imports belong at top level and relative ES module paths need extensions.
2.2 Load modules in browsers and Node.js
The same ES module syntax runs in a browser and in Node.js. Each environment still needs to know that a file is a module, and each has its own way to resolve the requested paths.
Start a browser module
<script type="module" src="main.js"></script>
Load only the entry module with a script tag. Its imports fetch the other files automatically. Module scripts without async are deferred: they run after HTML parsing, in document order. The page elements they access are then available.
Serve the folder over HTTP with a local server, such as python3 -m http.server or npx serve, and open its localhost address. Opening the HTML from disk usually causes module requests to fail under browser security rules. The server must also return JavaScript with a suitable MIME type, such as text/javascript.
Module declarations do not become globals. An inline handler such as onclick="save()" cannot find a function local to a module. Attach the handler from JavaScript instead:
const button = document.querySelector("#save");
function save() { console.log("Saved"); }
button.addEventListener("click", save);
Resolve paths and package names
The module specifier is the string naming the module. ./geometry.js is relative to the importing file; ../geometry.js moves up one folder. A path beginning / starts at the website root, while a full HTTPS URL names its complete location.
A bare name such as "date-fns" has no path prefix. A browser cannot search node_modules for it. An import map placed before the module scripts maps a bare name to a URL. A dev server or bundler can instead resolve installed packages and prepare them for the browser. Installing a package alone does not change the browser's loader.
Make Node.js's module format explicit
| File or setting | Node.js format |
|---|---|
.mjs | ES module |
.cjs | CommonJS |
.js under "type": "module" | ES module |
.js under "type": "commonjs" | CommonJS |
Node.js reads type from the nearest parent package.json. Without an explicit type, current versions can detect ES module syntax in an otherwise ambiguous .js file. Set "type": "module" in a new ES module project so tools agree about the intended format. The .mjs and .cjs extensions override that setting.
With the circle example's three files and { "type": "module" }, node main.js prints 28.27. The same files work through a browser module script served over HTTP.
Recognize CommonJS and built-ins
CommonJS is Node.js's older module system. It uses require() to load synchronously and module.exports to provide values. An ES module can default-import a CommonJS package's module.exports. Current Node.js can also require synchronous ES module graphs; top-level await in that graph prevents synchronous loading.
In an ES module, use import { readFile } from "node:fs/promises"; for a Node.js built-in. require, module, and __dirname are CommonJS variables, not ES module globals. import.meta.url gives the module's URL; recent Node.js also supplies import.meta.dirname for local file modules.
If an import statement is rejected as being outside a module, inspect the loader setting: type="module" in browser HTML, or the package type or extension in Node.js.
Key Takeaways
- A browser entry module uses
type="module"and is normally deferred. - Use a local server and attach event handlers from the module.
- Browsers need an import map or build tooling to resolve bare package names.
- Node.js uses file extensions and the nearest package's
typefield. - CommonJS uses
requireandmodule.exports; ES modules useimportandexport.
2.3 Load features on demand and separate responsibilities
A static import declares a dependency needed as part of loading a module. Dynamic import is an expression: it starts loading when execution reaches it and returns a promise. Use it for a feature that many visits never need.
Wait for the namespace object
const button = document.querySelector("#show-chart");
button.addEventListener("click", async () => {
try {
const { drawChart } = await import("./chart.js");
drawChart(measurements);
} catch (err) {
console.error("Could not load the chart module", err);
}
});
Here, measurements is the data the page has collected. The first click loads the chart module and calls its exported function. Later clicks reuse the evaluated module. The promise can reject on a loading or evaluation error, so the handler catches it.
import("./chart.js").drawChart tries to read a property on the promise, not on the loaded module. Await the promise or use .then. For a default export, access the namespace's default property: const { default: Chart } = await import("./chart.js");. Unlike a static declaration, this expression can occur inside a function or condition, and its path can be calculated at run time.
Wait during module setup
An ES module can use top-level await outside a function:
const response = await fetch("./config.json");
export const config = await response.json();
Modules depending on this module wait for its evaluation to finish before their own bodies run. Independent modules can continue. A slow configuration request therefore delays its dependents, not necessarily every part of the program. Classic scripts and CommonJS do not allow top-level await; use this module feature for setup its dependents truly need first.
Give each module one clear job
A to-do application can use these boundaries:
| Module | Responsibility |
|---|---|
todo.js | Export the Todo class |
storage.js | Load and save data through localStorage |
ui.js | Render the list in the page |
main.js | Connect modules and event handlers |
The data and storage modules do not import the UI. They can be tested and reused without creating page elements. Keeping calculation and storage out of event handlers also makes failures easier to locate.
Private top-level state is shared across importers. In let nextId = 1; export function createId() { return nextId++; }, the variable stays private while the function exposes controlled access. Two importers calling it once get 1 and 2, not two separate counters starting at 1.
Control work at import time
import "./setup.js"; is a side-effect import: it loads and evaluates the module without requesting local names. Side effects can be useful for setup, but a reusable module is easier to test when its top level defines features and its functions start the work explicitly.
An index or barrel file collects re-exports. It can simplify an interface, but large barrels can bring in extra code and hide a cycle. A circular dependency occurs when modules depend on one another, directly or through other modules. A cycle can work, but reading a binding before its exporting module initializes it causes a ReferenceError. Move shared definitions into a third module that both sides import to remove that dependency cycle.
Key Takeaways
import()returns a promise for a module namespace and can run inside a function.- Await dynamic loading and handle rejected promises.
- Top-level await delays dependent module evaluation.
- Separate data, storage, rendering, and event wiring into focused modules.
- One evaluated module can hold private state shared by all its importers.
- Move shared definitions into a third module to break a circular dependency.
3Run a repeatable project with npm
A project needs a clear description of its commands and the packages it depends on. npm keeps that information in package files and installs the code those files describe. Learn to read scripts before running them, distinguish development tools from product dependencies, and use version ranges together with a lock file. These choices let another computer recreate the project without copying an installed package folder.
3.1 Describe and run an npm project
npm is the package manager supplied with Node.js. The name refers both to a command-line tool and to its public package registry. A package is a folder described by package.json; a project's root is the folder containing that file.
Create the project description
Run node -v and npm -v to check the installed tools. Change to the intended project folder before running npm init. The interactive command asks questions; npm init -y accepts defaults. Then edit the file to describe the project.
{
"name": "shape-lab",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node src/main.js",
"test": "node --test",
"lint": "eslint ."
},
"dependencies": {},
"devDependencies": {}
}
This is JSON, not a JavaScript object literal. Keys and strings use double quotes. Comments and trailing commas are invalid. A parse error can stop npm before it runs any command.
The name is lowercase and URL-safe; published package names have a maximum of 214 characters. version describes this project, not its Node.js version. private: true prevents accidental npm publication. type: "module" makes Node.js load the package's .js files as ES modules.
Published packages can use main and exports to define entry points for their users. An optional engines entry, such as { "node": ">=22" }, states supported Node.js versions. Read those requirements when a project fails on an older installation.
Use scripts as the task menu
npm run lists available scripts. In this example, npm run lint runs eslint ., npm start runs the application, and npm test runs the test runner. start and test have these short forms; other task names normally use npm run.
npm temporarily adds the project's node_modules/.bin directory to the command search path. A locally installed tool can therefore be named directly in a script. Everyone using the project runs the same recorded tool instead of relying on a global installation.
Extra arguments follow --: npm run lint -- --fix passes --fix to ESLint. The script gives the task a stable name even if the team changes the tool behind it. Start with the scripts field when learning how to work on an unfamiliar project.
Read version numbers
Semantic versioning uses MAJOR.MINOR.PATCH. Starting from 2.4.1, a compatible bug fix becomes 2.4.2; a compatible feature becomes 2.5.0; a breaking change becomes 3.0.0. A major upgrade can require changes to code using the package. Before 1.0.0, a project is in early development and releases can change more freely.
Run an occasional package command
npx serve runs the installed command if available locally. Otherwise, npx can ask to download the package temporarily and run it. This is useful for a tool that does not need to become a permanent project dependency. Check the exact package name and the code's trustworthiness before installing or running it: package commands and install scripts can execute with your permissions.
A command run in the wrong folder may fail because package.json is missing, or an install may create package files in an unintended place. Check the current folder first. Keep scripts understandable and avoid operating-system-specific command tricks when the project must run on several systems.
Key Takeaways
package.jsondescribes the project, scripts, and dependencies using valid JSON.private: trueprevents npm publication;typesets the Node.js module format.- Use npm scripts to run locally installed tools consistently.
- Semantic versions separate breaking changes, compatible features, and fixes.
npxcan run a local command or temporarily download one.
3.2 Install and update the right package versions
A dependency is code a project relies on. npm records direct dependencies in package.json, installs packages in node_modules, and records the exact installed dependency tree in package-lock.json.
Separate product code from development tools
npm install date-fns adds a runtime package to dependencies. npm install --save-dev eslint adds a development tool to devDependencies; -D is the short form. Linters, formatters, test runners, bundlers, and TypeScript normally belong in the development list.
When another project installs a published package, npm installs that package's dependencies but not its development dependencies. A browser application can still bundle runtime dependencies into its final files even though the build tool itself is a devDependency.
npm install without a package name installs the dependencies listed for the project. npm uninstall <package> removes a package and its entry. After installing date-fns, Node.js and build tools can resolve import { format } from "date-fns";. A plain browser still needs a resolver such as an import map or dev server.
Read the allowed range
| Entry | Stable versions it allows |
|---|---|
^4.17.21 | At least 4.17.21, below 5.0.0 |
~4.17.21 | At least 4.17.21, below 4.18.0 |
4.17.21 | Exactly 4.17.21 |
^0.3.1 | At least 0.3.1, below 0.4.0 |
The caret is npm's usual default. For versions at or above 1.0.0, it permits compatible minor and patch updates. Below 1.0.0, it is stricter. For ^2.3.0, 2.3.5 and 2.9.0 fit; 2.2.9 is too old and 3.0.0 crosses the major limit.
A permitted range does not identify one installed version. The lock file does that, including transitive dependencies, which are the packages your chosen packages need.
Recreate an installation
Share package.json and package-lock.json with the project. Let npm maintain the lock file. The generated node_modules folder can be rebuilt and is not the file to share.
Use npm install during development when dependency records may change. Use npm ci for a clean, repeatable install: it requires the lock file, removes existing node_modules, installs the locked tree, and does not rewrite either package file. It fails if the package file and lock file disagree. Repair that disagreement deliberately during development; deleting the lock file can silently change many versions.
Update with a reason
npm outdated compares current, wanted, and latest versions. Wanted is the newest version the declared range permits; latest can be a major version outside it. npm update updates within the ranges and records the resulting tree. To cross a major boundary deliberately, read the changelog, run npm install <package>@latest, and check the project with its tests.
Install project tools locally so their versions are recorded. Before adding another dependency, check maintenance, adoption, license, and whether a built-in feature already solves the problem. structuredClone() handles supported deep-copy needs; Intl.DateTimeFormat formats dates without a date package.
npm audit reports known vulnerabilities in installed versions. Read the report to understand the affected code and usage. npm audit fix can apply compatible fixes, but it does not prove that every vulnerability is resolved or that an update preserves the project's behavior.
Key Takeaways
- Use
dependenciesfor runtime code anddevDependenciesfor development and build tools. - Version ranges allow releases; the lock file records exact installed versions.
- Share both package files and rebuild
node_modules. npm ciinstalls the lock file without changing it and rejects mismatched package files.npm updatestays inside declared ranges; major upgrades need a deliberate choice.- Read audit reports and check behavior after dependency updates.
4Check and build code with developer tools
Different tools answer different questions about a project. A linter looks for suspicious code, a formatter arranges it, tests check its behavior, and a build prepares browser files for release. A dev server keeps the editing loop fast. Learn what each result tells you and what it leaves unchecked, so a successful formatting command or development page is not mistaken for evidence that the whole application works.
4.1 Find code problems and format consistently
A linter reads source code without running it and reports likely bugs or risky patterns. A formatter rewrites layout consistently. ESLint and Prettier often work together because those jobs solve different problems.
Configure the checks
npm init @eslint/config@latest asks about the project, installs development packages, and creates a configuration file. Current ESLint uses flat configuration, commonly in eslint.config.js.
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
languageOptions: { globals: globals.browser },
rules: {
eqeqeq: "error",
"no-unused-vars": "warn",
},
},
];
The recommended configuration enables bug-finding rules. The browser globals tell ESLint that names such as document exist. This avoids treating a real environment feature as an undefined name.
Rule severities are "off", "warn", and "error", also written as 0, 1, and 2. Errors cause a failure exit code; warnings alone do not fail the command by default. A warning is still information to read, even if the script exits successfully.
Read a lint report
function total(items) {
let sum = 0;
let unused = 5;
for (const item of items) {
if (item.quantity == "2") sum += item.price;
}
return summ;
}
Under the configuration above, this reports five problems. total is never used. sum is updated but its value is never used beyond those updates. unused is unused. The rule eqeqeq flags ==, and no-undef catches the misspelled summ. Calling this function would fail at that undefined name, but the linter can find it without a call.
Start with the rule name and location in the report. Correct the intended behavior, then rerun the command. npm run lint -- --fix requests safe automatic fixes when the project has a lint script. It cannot decide the right business logic for every warning. Disable a rule on one line only when there is a reason, and record that reason next to the comment.
Give layout to one formatter
npm install --save-dev --save-exact prettier
npx prettier . --write
npx prettier . --check
--write changes files; --check reports formatting differences without changing them. With defaults, const point={x:1,y:2} becomes const point = { x: 1, y: 2 };. Options such as "singleQuote": true go in .prettierrc.
| Tool | Main question |
|---|---|
| ESLint | Does this code contain a pattern a rule flags? |
| Prettier | Does the code have the chosen layout? |
| Test runner | Does the code produce the expected behavior? |
ESLint's recommended rules do not prescribe Prettier's layout, so that setup avoids formatting conflicts. If another ESLint configuration adds conflicting style rules, eslint-config-prettier turns those rules off. Running two competing formatters can make files change back and forth.
Add lint and format scripts so checks are easy to repeat. Editor extensions can show diagnostics while typing and format on save after configuration. Both tools skip node_modules by default; add generated output such as dist to appropriate ignore settings. A lint-clean function can still calculate perimeter when it should calculate area; behavior needs tests.
Key Takeaways
- A linter finds configured source-code problems without executing the program.
- ESLint error severity fails the command; warnings do not fail it by default.
- Prettier changes layout with
--writeand checks it with--check. - Use compatible lint and formatting rules, plus tests for behavior.
4.2 Develop locally and build browser files
A browser can load a few ES modules directly, but larger projects also use installed packages and files that need transformation. A dev server prepares files while you edit. A bundler follows dependencies to produce files suitable for release.
Work through the Vite cycle
npm create vite@latest shape-lab
cd shape-lab
npm install
npm run dev
npm run build
npm run preview
Choose the Vanilla JavaScript template for this project. The generator supplies development and build scripts. npm run dev prints a local address, often http://localhost:5173. Open that address while editing. In Vite, index.html is the entry point and typically loads /src/main.js with a module script.
The dev server resolves package names and transforms files as needed. Saving a file triggers an update. Hot module replacement, or HMR, replaces a changed module in the running page; supported updates can preserve state instead of forcing a full reload.
npm run build writes optimized output to dist. npm run preview serves that output locally so you can check the actual production build. Preview needs an existing build and does not create a fresh one. Edit source files, since another build replaces generated output.
Understand the production changes
| Build feature | Purpose |
|---|---|
| Bundling | Combine modules into fewer browser files |
| Tree shaking | Remove code the build can prove unused |
| Minification | Reduce size by removing layout and shortening names |
| Content hashes | Change asset names when contents change, supporting caching |
| Code splitting | Keep some code in separate chunks that can load later |
For example, a helper module may export both formatArea and formatVolume. If only formatArea is used and removing the other code is safe, tree shaking can omit formatVolume. Static imports and exports make dependencies visible. A module's top-level side effects may need to remain even when none of its exports are used.
A dynamic import() gives the build tool a boundary for loading later. Build tools can create separate chunks for it; chunk layout depends on the build, so a source module does not always become exactly one output file.
A name such as assets/index-4f2a9c1b.js changes with its content, allowing long-lived browser caches without serving an old file after an update. A source map connects generated locations back to original source lines, making stack traces and debugging easier when maps are available.
Include assets and public configuration
Vite also processes assets: import "./style.css"; includes styles, and import logoUrl from "./logo.png"; supplies the image's final URL. The build can update those references to match generated filenames.
Read mode information through import.meta.env.MODE. Its default values are "development" for development and "production" for a build; a custom mode can use another name. Custom environment values beginning VITE_ are exposed to browser code by default. Everything shipped to the browser is public: keep secret keys and passwords on a server, outside browser code and exposed environment variables.
Do not check only the dev page. Build and preview before release to find problems in transformed output and asset paths. Opening dist/index.html directly from disk does not reproduce serving the site over HTTP. A small page with a few plain modules may need only a static server, without a full build tool.
Key Takeaways
- A dev server resolves and transforms files during editing; a production build creates release output.
- HMR can replace changed code while preserving supported page state.
- Tree shaking removes code only when the build can safely omit it.
- Content hashes support caching, and source maps connect output to source.
- Build first, then preview the generated output locally.
- Browser bundles and exposed environment values are public.
4.3 Test behavior with assertions
An automated test runs code with known inputs and checks the result using assertions. A unit test focuses on a small piece, such as a function or method. The runner finds tests, executes them, reports failures, and exits unsuccessfully when a test fails.
Arrange, act, assert
Arrange the input, act by calling the code, and assert the expected result. A useful test name describes behavior. For a rectangle class whose constructor rejects a negative side:
import { test } from "node:test";
import assert from "node:assert/strict";
import { Rectangle } from "./rectangle.js";
test("area multiplies width by height", () => {
assert.equal(new Rectangle(3, 4).area(), 12);
});
test("rejects a negative width", () => {
assert.throws(() => new Rectangle(-1, 4), RangeError);
});
The first assertion checks 12 cm², an independently known result. The second passes a function to assert.throws so the assertion can observe the error. Running node --test discovers names such as rectangle.test.js; a package script "test": "node --test" enables npm test.
Strict assertion mode uses Object.is for assert.equal without coercion. assert.deepEqual compares object and array contents. Distinct arrays containing the same values are not the same object, so a strict identity comparison is the wrong check for equal contents.
Use equivalent ideas in Vitest
import { describe, it, expect } from "vitest";
import { Rectangle } from "./rectangle.js";
describe("Rectangle", () => {
it("computes the area", () => {
expect(new Rectangle(3, 4).area()).toBe(12);
});
it("rejects a negative width", () => {
expect(() => new Rectangle(-1, 4)).toThrow(RangeError);
});
});
Vitest uses describe for grouping and it for individual tests. Run npx vitest to watch for changes, or npx vitest run for one run. Jest offers a similar style. Use toEqual for contents and toBe for identity or primitive values.
Include boundaries and numerical limits
Test the exact endpoints of an allowed range as well as inputs just outside it. A thermostat accepting 10–30 °C needs tests accepting 10 and 30, plus tests rejecting 9.5 and 30.5. Add separate checks for nonnumeric and nonfinite values if the interface rejects them. Tests should not depend on a previous test's modified object or execution order.
Floating-point arithmetic may not match a written decimal exactly: 0.1 + 0.2 is 0.30000000000000004. For an appropriate tolerance, use assert.ok(Math.abs(actual - expected) < 1e-9). A circle of radius 3 has an area of about 28.274333882 cm²; Vitest's toBeCloseTo(28.27, 2) checks closeness at that decimal precision. Choose the tolerance to match what the test needs to establish.
Learn from a failure
Read the named behavior, expected and actual values, and source location. Decide whether the implementation or the test's expectation is wrong. In test-driven development, write a failing test, implement enough to pass, then improve the code while keeping tests green.
Export logic from modules and pass inputs as arguments. An event handler can then gather page data and call that logic, instead of mixing calculations into DOM work. DOM-dependent code may use a simulated browser such as jsdom. Await asynchronous work in an async test so it cannot finish before its checks. A test that simply repeats the implementation's formula can repeat its bug; use expected behavior and independent examples.
Key Takeaways
- Tests run code and use assertions to check independently expected behavior.
- Arrange inputs, act, and assert one behavior with a useful test name.
- Use content comparisons for distinct arrays or objects with equal contents.
- Test boundaries and invalid inputs, and use suitable floating-point tolerances.
- Await asynchronous work and keep tests independent of execution order.
5Place types and frameworks in the workflow
TypeScript and front-end frameworks build on the JavaScript tools already introduced. Types help an editor and checker find mismatched values before execution. Components help organize interfaces that share and change data. Neither removes the need to understand modules, dependencies, or tests. Use the project scripts as a map of how these pieces cooperate, then add tools when the work gives them a clear purpose.
5.1 Understand what TypeScript checks
TypeScript adds static type syntax to JavaScript. A type describes what kind of value code expects. The checker uses those descriptions before the program runs, which can expose mistakes before a user reaches them.
Check a call before execution
function area(width: number, height: number): number {
return width * height;
}
area(3, "4"); // type error: the second argument is a string
Both parameters must be numbers. The written "4" is a string, so the checker reports the mismatch. Plain JavaScript would coerce that string in multiplication: 3 * "4" gives 12. With addition, 3 + "4" gives "34". A type error identifies a mismatch in the intended inputs even when JavaScript could execute the expression.
An interface describes an object's shape:
interface Shape {
name: string;
area(): number;
}
A class declared with implements Shape must provide the specified properties and methods with compatible types. The checker can also detect misspelled properties, incorrect argument counts, and possible undefined values when strict checking is enabled.
Separate checking from removing types
| Tool or action | What it does |
|---|---|
tsc | Checks types and normally emits JavaScript |
tsc --noEmit | Checks types without writing output |
| Vite's TypeScript transformation | Removes type syntax for execution |
| Node.js type stripping | Runs supported erasable TypeScript after stripping types |
TypeScript project settings live in tsconfig.json. A Vite build does not itself establish that all types are correct. Projects commonly run tsc --noEmit separately. Current Node.js can run supported .ts files directly, but its type stripping also does not type-check them.
Keep runtime checks at data boundaries
Type annotations are removed before execution. Declaring the expected type of data from fetch() or JSON.parse() does not verify the actual value. Check external data in running code before relying on it. A server can send a string even when a type declaration says a field is numeric.
TypeScript's private and readonly are checker restrictions; they do not provide JavaScript's runtime # privacy. JavaScript private fields continue to be enforced when the program runs. TypeScript also does not make a program run faster or prove that its calculations are correct.
The type any turns off useful checking for a value. It may silence an error while allowing the original mistake to survive. Fixing an incorrect assumption or adding a real data check gives the checker better information.
Adopt it in manageable steps
A JavaScript file can begin with // @ts-check and use JSDoc such as /** @param {number} width */ to get checks in supporting editors without becoming a .ts file. A project can later rename files one at a time. Libraries may supply .d.ts type definitions; community definitions are often distributed under @types/....
Larger, long-lived projects gain clearer interfaces, safer refactoring, and better completion in the editor. A small script may not justify another build step and new syntax yet. Tests remain useful in either case: a correctly typed area function can still return the wrong numeric calculation.
Key Takeaways
- TypeScript checks expected value types before execution.
- Removing type syntax is separate from checking types.
- Type declarations do not validate external data while the program runs.
- JavaScript
#privacy is enforced at runtime; TypeScriptprivateis checked statically. - JSDoc checking allows gradual adoption before converting files to TypeScript.
5.2 Combine tools around the project's needs
A library supplies code your program calls. A framework provides an application structure and calls your code at the right times. This change in control helps coordinate larger interfaces, but it still rests on JavaScript, modules, and a project toolchain.
Organize an interface into components
React, Vue, Svelte, and Angular organize user interfaces with components: reusable parts such as a navigation bar or a to-do item. A component combines markup and behavior, often with styles. When its state changes, the UI system coordinates the relevant page updates.
Meta-frameworks such as Next.js, Nuxt, SvelteKit, and Astro add a larger setup, including routing, server rendering, and build support. Their exact features differ. Knowing JavaScript functions, events, asynchronous code, and module boundaries helps when learning any of them.
A page with a few independent interactive features may be clear with plain JavaScript. A framework becomes useful when many views must share changing data. Choose it for that need instead of assuming every page requires the largest toolchain.
Trace how the tools cooperate
Framework packages are installed through npm and imported as modules. Build tools transform syntax such as JSX or framework-specific files. TypeScript may check interfaces between parts. Linting, formatting, and testing keep their distinct jobs regardless of the UI choice.
Start a simple project and add the tools used by the team:
npm create vite@latest shape-lab
cd shape-lab
npm install
npm install --save-dev eslint @eslint/js globals prettier vitest
A Vite template supplies development and build tooling. Installing extra packages does not configure them: add the ESLint configuration, create tests, and set up editor extensions for linting and formatting on save. A scripts field can provide these tasks:
{
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier . --write",
"test": "vitest run"
}
These entries belong inside "scripts" in package.json. While editing, run npm run dev; the editor can lint and format as files change. Before sharing work, run npm test. Before release, run npm run build and npm run preview. A static site's built dist folder can then be uploaded to a suitable static host.
Find the file that controls a task
| File or folder | Role |
|---|---|
package.json | Commands and direct dependency ranges |
package-lock.json | Exact dependency tree |
node_modules/ | Generated installed packages |
eslint.config.js | Lint rules and environments |
.prettierrc | Formatting options |
vite.config.js | Development and build options |
tsconfig.json | TypeScript checking and compilation settings |
dist/ | Generated build output |
Begin an unfamiliar project with package.json: its scripts reveal how the project runs, and its dependency lists reveal which tools those scripts need. Generated folders can be recreated; settings and source explain how to recreate them.
Learn new tools one at a time so an error still has a recognizable cause. Use official documentation for the installed version when commands or defaults differ, since tool and framework releases change more often than core JavaScript syntax.
Key Takeaways
- A library is called by your code; a framework calls your code within its structure.
- Components coordinate reusable UI parts and changing state.
- Framework projects still depend on modules, packages, builds, and checks.
- Templates and package installation do not configure every extra tool automatically.
- Read package scripts first to learn a project's workflow.
- Add tools to solve a concrete need and check the documentation for the installed version.
JavaScript Classes and Modules examples
1Build a small family of objects
18 steps
- 1IntroductionStart with objects that need behavior
A program can store many rectangles, but each one also needs operations such as area and perimeter. A class lets those objects share the operations while keeping their dimensions separate.
Follow the object from creation to a method call. Then add guarded state, distinguish class-level values, and specialize behavior with inheritance. Finally, inspect the prototype chain to see where the shared methods live.
- 2ExampleInitialize one instance
The constructor stores values on the new object. Here the dimensions are in centimeters:
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } const a = new Rectangle(3, 4);newcreatesaand runs the constructor withthisreferring to it. The two assignments giveaits own width and height. - 3ExampleCall shared behavior with separate data
Add
const b = new Rectangle(5, 2);. Both objects can callarea, but each call reads the receiver's dimensions.a.area()multiplies 3 by 4 and returns 12 cm².b.area()multiplies 5 by 2 and returns 10 cm². Assigningb.width = 8changesbonly, so its next area is 16 cm² whileastays at 12 cm².The method does not need a separate copy of its code for every rectangle. The data it reads comes from
this. - 4TipKeep the object in a callback
Taking a method off an object does not permanently attach its receiver:
const calculate = a.area; // calculate(); // this is undefined const calculateSafely = () => a.area();Class methods run in strict mode. The detached call has no object before the dot, so reading
this.widthfails. The arrow wrapper keeps the original method call intact.a.area.bind(a)is another way to produce a callback tied toa. - 5ChallengePredict a second call
A new
Rectangle(6, 2)is assigned totile. The statementtile.width = 7runs. What doestile.area()return, and why does this not change another rectangle created earlier?---reveal---
It returns 14 cm²: the method reads the receiver's current width, 7 cm, and height, 2 cm. Each instance stores its own dimensions, so changing
tile.widthdoes not update another instance. Sharing a method does not make the instance fields shared. - 6ExplanationStore state behind a setter
A thermostat can expose
targetwhile storing#targetprivately. A getter reads the private value; a setter checks a requested change.The setter rejects nonfinite or nonnumeric input and values outside 10–30 °C before it assigns
this.#target = value. That order matters: throwing before assignment preserves the last valid target. The separate storage name also avoids a setter repeatedly calling itself. - 7ExampleTrace an accepted and rejected change
Start with a thermostat target of 20 °C. Assigning 22 passes both checks, so the setter stores 22. Assigning 35 then throws a
RangeError; the stored value stays 22.Number.isFinite(value)also rejects"22",NaN, and infinity. The range comparisons alone would not reject every invalid type or value. Private storage controls direct access, while validation controls what the public interface accepts. - 8Key InsightChoose class or instance storage
A field declared with
staticbelongs to the class. Read the thermostat's lower limit asThermostat.MIN, whilet.targetreads one instance's state.A static count can track how many objects have been constructed. A normal field instead starts separately on every new object. Inside a static method,
thisis the class receiving the call, so it does not provide one instance's target or dimensions. - 9ChallengeUse the public path
A subclass of
Thermostatneeds to raise its target from 20 to 24 °C. The parent declares#targetand a validating publictargetsetter. How should the subclass request the change, and what would directthis.#targetaccess do?---reveal---
Assign
this.target = 24so the inherited setter validates and stores the value. The subclass cannot name the parent's private field directly; that use of#targetis a syntax error. Inherited accessors still run in the class that declared the private state. - 10ExampleGive related classes a parent
A
Shapeconstructor stores a name, and itsdescribe()method callsthis.area(). A circle specializes the calculation:class Circle extends Shape { constructor(radius) { super("Circle"); this.radius = radius; } area() { return Math.PI * this.radius ** 2; } }superinitializes the parent part beforethis.radiusis used. A circle is a shape, so the inherited description fits its role. - 11ExampleLet the receiver choose the calculation
With radius 2 cm,
new Circle(2).describe()returns"Circle with area 12.57 cm²". The description method is inherited, but itsthis.area()call finds the circle's override.A rectangle can inherit that same description and supply multiplication as its own area method. A loop calling
describe()on both objects needs no separate circle and rectangle branches. This shared interface with different behavior is polymorphism. - 12ExplanationExtend a parent method
A square can call
super(side, side)in its constructor to give a rectangle parent equal dimensions. An overridden description can then callsuper.describe()and append the side length.super(...)in the constructor andsuper.describe()in a method have different jobs: one initializes the parent, the other invokes a parent method. A subclass without its own constructor forwards arguments automatically. - 13ChallengeChoose a useful model
A
Reportobject must send messages through aNotifier. Reports are not notifiers. How should the objects be related, and how would the report request a notification?---reveal---
Use composition: give the report a notifier property, then call a method such as
this.notifier.send(message). This is a has-a relationship. ExtendingNotifierwould claim that every report is itself a notifier just to reuse its code, which does not match the model. - 14ExplanationFind a method through the chain
A circle's own properties hold data such as its radius. When it lacks its own
describe, JavaScript searchesCircle.prototype, thenShape.prototype, where that method is stored.The ordinary chain continues through
Object.prototypetonull. If no object in the chain has a requested property, reading it yieldsundefined.instanceof Shapenormally succeeds becauseShape.prototypeappears in the instance's chain. - 15ExampleShadow an inherited setting
Create
settingswithObject.create(defaults), wheredefaults.fontSizeis 16. Assignsettings.fontSize = 18.The assignment creates an own data property that shadows the inherited one. The default remains 16. Deleting
settings.fontSizeremoves the shadow, so the next read finds 16 on the prototype again. This trace assumes an ordinary data property; an inherited setter can instead handle an assignment. - 16ChallengeCheck ownership separately from access
An object
panelinheritstheme: "light"from a plain prototype and has no owntheme. What dopanel.theme,Object.hasOwn(panel, "theme"), and"theme" in panelproduce?---reveal---
They produce
"light",false, andtrue. Reading andincan find inherited properties, whileObject.hasOwnasks only about storage onpanelitself. A successful read does not establish ownership.Object.keys(panel)also leaves out this inherited key. - 17ChallengeConnect inheritance, privacy, and lookup
Sensordeclares#value, a public gettervalue, and a methoddescribe()that readsthis.value.IndoorSensor extends Sensoradds no members. Why can an indoor sensor calldescribe()even though its class body cannot name#value?---reveal---
Lookup finds the inherited method, which runs with the indoor sensor as
this. The getter declared inSensorcan read its own private field on that instance. Inheritance supplies the public behavior without granting the subclass direct access to the parent's private name. - 18RecapInspect an object in a useful order
When tracing a class instance, ask where its data is stored, which object receives the method call, and where lookup finds the method. If a change goes through a setter, trace validation before assignment.
For an unfamiliar hierarchy, inspect
extendsand constructor arguments before following overrides. Use ownership checks when you need stored keys, and follow prototypes when you need to explain inherited behavior.
2Connect a program across module boundaries
13 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow one dependency graph
- 2ExampleExport the calculation
- 3ExampleExport and use the class
- 4ChallengeRead a changed export
- 5ExampleStart the files in a browser
- 6ExplanationResolve a package name
- 7ChallengeChoose the Node.js loader
- 8ExampleDelay an optional chart
- 9Key InsightIdentify what waits for setup
- 10ChallengeBreak an unnecessary cycle
- 11TipKeep work out of imports
- 12ChallengeCombine loading with a clear interface
- 13RecapTrace a module error by stage
3Recreate an npm project on another computer
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionMake the project reproducible
- 2ExampleCreate the package description
- 3ExampleRead the task before running it
- 4ChallengeIdentify a version change
- 5ExplanationRecord code and tools separately
- 6ExampleRead what a range permits
- 7ExplanationInstall the recorded tree
- 8ChallengeInterpret a blocked clean install
- 9TipPlan a deliberate update
- 10ChallengeCombine a range and a clean build
- 11RecapRead the two package records together
4Check a project from edit to build
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionAsk each tool a different question
- 2ExplanationConfigure a useful lint report
- 3ExampleFind a typo before execution
- 4ChallengeChoose a formatting check
- 5ExampleRun development, build, and preview
- 6ExampleTrace a build optimization
- 7ConnectionConnect output to the source
- 8ChallengeChoose what to rebuild
- 9ExampleWrite an independent expectation
- 10Key InsightCompare contents and identity correctly
- 11ChallengeTest the exact boundary
- 12TipWait for an asynchronous test
- 13ExampleUse a sensible numerical tolerance
- 14ChallengeInvestigate a green build with a wrong result
- 15RecapRead failures as evidence
5Fit types and components into a working project
10 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionAdd a tool for a specific need
- 2ExampleCatch a mismatched call
- 3ExplanationSeparate transformation from checking
- 4ChallengeCheck data that arrives at runtime
- 5ExplanationPlace a component in the workflow
- 6ExampleRead the project task menu
- 7ChallengeChoose when a framework helps
- 8ChallengeFind the missing check
- 9TipAdopt checking gradually
- 10RecapFollow each tool to its evidence
JavaScript Classes and Modules practice questions
Set 1: Read classes, imports, and project tasks10 Practice Questions
Question 1 of 10
LIMIT by name. How can one declaration import the function as calculate and the constant under its exported name?Show answerHide answer
{ calculate, LIMIT } asks for two named exports, but the function was provided as the default. The local name calculate is chosen by the importer and need not match the function's declaration name.Question 2 of 10
static created = 8;. Its instance is assigned to batch, and the class is named Batch. How is the stored count read?Show answerHide answer
Batch.created. Reading batch.created searches the instance and its instance prototype chain, where this static field was not placed. Static does not mean a field is copied onto every object.Question 3 of 10
Why does this constructor fail, and what order fixes it?
class Item {
constructor(code) { this.code = code; }
}
class Box extends Item {
constructor(code) {
this.open = false;
super(code);
}
}
new Box("K7");
Show answerHide answer
this until the parent constructor has initialized the instance through super(code). Move that call before the open assignment. Assigning another field first would have the same problem, because it would still access this too early.Question 4 of 10
export default class Badge {}. What import binds that class to the local name Tag?Show answerHide answer
Tag. Importing { Badge as Tag } instead requests a named export called Badge, which this module does not provide. A namespace import would bind an object containing the default export, not the class itself.Question 5 of 10
import declaration but its script tag has no type attribute. What HTML change makes it an ES module?Show answerHide answer
type="module" tells the browser to use ES module syntax and loading rules. Adding defer alone changes timing for a classic script but does not make import declarations valid there. The module entry can then load its dependencies automatically when the files are served correctly.Question 6 of 10
"scripts": { "check": "eslint ." }. What command runs that named task?Show answerHide answer
npm run, so this task is npm run check. npm check does not acquire a short form just because a script has that name; the familiar npm test and npm start forms are special cases. The script runs the recorded ESLint command.Question 7 of 10
Show answerHide answer
devDependencies, normally added with npm install -D. The scripts section can contain a command that runs it, but that does not record the package dependency. Runtime library code belongs in dependencies.Question 8 of 10
Show answerHide answer
Question 9 of 10
"a" and "b". How should a Vitest test check those contents against a separate array literal?Show answerHide answer
toEqual checks the array's contents, including the order of its values. toBe and the identity expressions compare whether both operands are the same object; a separate array literal creates a different object. Equal contents therefore require the deep comparison shown.Question 10 of 10
Show answerHide answer
Set 2: Apply modules and repeatable project settings10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What happens at the last line?
class Meter {
value = 2;
read() { return this.value; }
}
const meter = new Meter();
const read = meter.read;
read();
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What is the last result?
const base = { unit: "cm" };
const sample = Object.create(base);
Object.hasOwn(sample, "unit");
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What does the final expression return?
class Label {
constructor(text) { this.text = text; }
length() { return this.text.length; }
}
new Label("Orbit").length();
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
import("./help.js") return immediately?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
~6.4.2. What is the newest stable version allowed among 6.4.9, 6.5.0, 7.0.0, and 6.4.1?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
loadItems() without awaiting it. The test callback ends; the promise later fulfills with a new array. The intended assertion uses toBe(["x"]). What must change to make the test check the promised contents reliably?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
.ts program runs in Node.js through supported type stripping. Why does that successful run not establish that all its declared types are correct?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 3: Diagnose classes and project failures10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What are the final readings?
class Counter {
count = 1;
add() { this.count += 2; }
}
const a = new Counter();
const b = new Counter();
const addA = a.add.bind(a);
b.count = 10;
addA();
const callB = () => b.add();
callB();
[a.count, b.count];
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
#level and provides a public getter on its prototype. It defines no toJSON method. What does JSON.stringify(new Gauge()) produce?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
class Photo extends Asset {} is declared with no constructor. Asset stores the argument passed to its constructor. What happens to an argument in new Photo("P8")?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
let count = 1, an object state = { count: 1 }, and advance(), which increases both counts. Two other modules import these same exports. The first calls advance() then assigns state.count = 9. What does the second read as [count, state.count], and can it reassign count?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
save() and a button uses inline onclick="save()". The module loads correctly, but clicking reports that save is undefined. How should the event be connected?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
"inspect": "eslint src". What command runs that script with ESLint's --fix argument?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
no-undef. What setup removes the formatting conflict while preserving the useful bug check?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
--write command directly change?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
A guard must accept finite numeric lengths from 0 through 12 cm. What three test expectations expose distinct faults in this implementation?
function setLength(n) {
if (n <= 0 || n > 12) throw new RangeError();
return n;
}
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
dist folder in that workflow?Show answerHide answer
Save this lesson to see this answer.
Set 4: Trace state, dependencies, and build behavior10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
"8" is caught, what value remains and why?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What are the final readings?
const base = { value: 4, read() { return this.value; } };
const x = Object.create(base);
const y = Object.create(x);
x.value = 7;
y.value = 10;
delete y.value;
[y.read(), Object.hasOwn(y, "read"), base.value];
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What are the final two values?
const defaults = { size: 11 };
const item = Object.create(defaults);
item.size = 15;
delete item.size;
[item.size, defaults.size];
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
"tiny-calendar". The package is installed locally, but the browser reports an unresolved specifier. What kind of missing setup explains this?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
main.js statically imports config.js, which awaits a network response before exporting settings. An independent entry clock.js imports neither file. Later, main's async handler runs const plugin = await import("./plugin.js"), whose only export is default class Plugin. What correctly describes waiting and construction?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
^8.1.0 and lock a dependency at 8.1.3. A clean build runs npm ci after 8.7.0 is published. What version is installed?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Set 5: Combine the ideas in unfamiliar projects10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What does new LoudNote("A").describe() return?
class Note {
constructor(text) { this.text = text; }
label() { return this.text; }
describe() { return "[" + this.label() + "]"; }
}
class LoudNote extends Note {
label() { return super.label() + "!"; }
describe() { return super.describe() + "?"; }
}
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does the final call return?
class Base {
constructor(n) { this.n = n; }
value() { return this.n; }
report() { return this.value() + 1; }
}
class Double extends Base {
value() { return super.value() * 2; }
}
class Extra extends Double {
report() { return super.report() + 3; }
}
new Extra(4).report();
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
feature.js has a default export and named ready. A barrel contains only export * from "./feature.js";. What does the barrel forward?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
document in Node.js. What restructuring addresses both failures?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
"private": true in package.json directly prevent?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
^0.6.2, locks 0.6.4, and has a valid matching lock file. The registry offers 0.6.9 and 0.7.0. What sequence describes the version after npm ci, then the newest allowed version after a deliberate npm update?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
VITE_ variable. Why is that credential unsuitable for this location even after minification?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
RangeError. What Vitest assertion lets the matcher observe the throw?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
private value, assigns parsed network input to it, and returns a numeric result with the wrong formula. The project passes tsc --noEmit. What combination addresses runtime privacy, external input, and behavior?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Arrange–act–assert
Assertion
B
Bare specifier
Barrel file
Boundary test
Bundler
C
Circular dependency
Class
Rectangle class can create several shapes whose area methods use their own widths and heights.Clean install
Code splitting
CommonJS
require calls and module.exports to provide values..cjs file remains CommonJS even when the surrounding package explicitly selects ES modules.Component
Composition
Constructor
Constructor function
new to initialize objects, often paired with methods assigned to its prototype.area to Rectangle.prototype after defining a constructor function.Content hash
D
Default export
Circle even if another file gives it a different local name.Deferred script
Dependency
Dependency audit
Dev server
Development dependency
-D so its version is recorded for the project.Dynamic import
import() expression that loads a module on demand and returns a promise for its module namespace.E
Encapsulation
Enumerable property
Object.keys lists own enumerable string keys, while for...in can also include inherited ones.ES module
import and export syntax, with its own scope and strict execution rules.F
Flat configuration
Formatter
Framework
G
Getter
thermostat.target can invoke a getter that returns the stored private temperature target.H
Hot module replacement
I
Import map
Inheritance
Inherited property
Object.hasOwn reports that it has no own theme key.Instance
Instance field
Interface
L
Library
Linter
Live binding
Lock file
npm ci to recreate the locked tree on another computer.M
Meta-framework
Method
tile.area() lets the method read the dimensions of tile through the receiver this.Method override
area() while continuing to inherit the parent's shared description method.Minification
Mixin
Module namespace
default when a default export exists.Module specifier
"../utils.js" asks the loader for a module one folder above the importing file.N
Named export
MAX, and an importer can request it directly or rename it locally.npm
npm script
npm run lint.npx
O
Own property
P
Package
Polymorphism
describe() on circles and rectangles, and each object supplies the appropriate area.Private field
#, directly accessible only within the class that declares the name.Private method
#, keeping helper operations outside the public interface of the object.this.#check(value) to validate input without exposing that helper to outside code.Prototype
null, linked to another object and searched when that object lacks a requested own property.Prototype chain
null when no further prototype exists.Shape.prototype after lookup passes through Circle.prototype.Public interface
R
Re-export
Receiver
this for an ordinary method call.box.open(), the receiver is box; detaching open does not permanently bind that receiver.Rule severity
Runtime validation
S
Semantic versioning
Setter
Shadowing
Side effect
Source map
State
Static analysis
Static factory
Rectangle.square(5) to create an instance with equal side lengths.Static initialization block
Static member
Subclass
extends that inherits from one superclass and can add members or specialize inherited behavior.Superclass
T
Test runner
Test-driven development
Tolerance
Top-level await
Transitive dependency
Tree shaking
Type annotation
Type definition
Type stripping
TypeScript
U
Unit test
V
Version range
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.