JavaScript Classes and Modules: Developer Tools and npm

Learn how professional JavaScript projects are organized. Build objects with classes, private fields and inheritance, and see the prototype chain that classes are built on. Split code into ES modules that run in the browser and in Node.js, manage packages with npm and 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

  1. 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 new
      Write a class with a constructor, fields, methods, getters and setters, create instances with new, and keep this pointing at the right object.
    • Protect data with private members and share values with static members
      Hide 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 super
      Create subclasses that inherit and override behavior, call the parent with super, check types with instanceof, and decide when inheritance or composition fits better.
    • Explain the prototypes behind objects and classes
      Describe how JavaScript finds properties along the prototype chain, how classes and extends set up that chain, and why built-in prototypes are left unchanged.
  2. 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 files
      Split 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.js
      Load 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 modules
      Use dynamic import() and top-level await, split an application into focused modules, and avoid problems with side effects and circular imports.
  3. 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.json
      Create a project with npm init, read and edit the main package.json fields, run tasks with npm scripts and npx, and read semantic version numbers.
    • Install, update and choose dependencies
      Install packages as runtime or development dependencies, read version ranges, use package-lock.json and npm ci for repeatable installs, keep dependencies updated and choose packages safely.
  4. 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 automatically
      Use 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 bundler
      Explain 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 runner
      Write 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.
  5. 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 JavaScript
      Describe 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 toolchain
      Explain 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.

JavaScript Classes and Modules study guide

5 chapters

1

Build 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

  • new creates an instance and runs its constructor; this identifies 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 extends for an is-a relationship and composition for a has-a relationship.
  • Call super(...) with the needed parent arguments before using this.
  • 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.

CheckProperties it considers
Object.hasOwn(obj, key)Own properties, enumerable or not
Object.keys(obj)Own enumerable string keys
key in objOwn and inherited properties, enumerable or not
for...inEnumerable 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.
2

Connect 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.

ExportCorresponding import
export const MAX = 30;import { MAX } from "./limits.js";
export default class Circle {}import Circle from "./shapes.js";
A default plus named round2import 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 settingNode.js format
.mjsES module
.cjsCommonJS
.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 type field.
  • CommonJS uses require and module.exports; ES modules use import and export.

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:

ModuleResponsibility
todo.jsExport the Todo class
storage.jsLoad and save data through localStorage
ui.jsRender the list in the page
main.jsConnect 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.
3

Run 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.json describes the project, scripts, and dependencies using valid JSON.
  • private: true prevents npm publication; type sets the Node.js module format.
  • Use npm scripts to run locally installed tools consistently.
  • Semantic versions separate breaking changes, compatible features, and fixes.
  • npx can 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
EntryStable versions it allows
^4.17.21At least 4.17.21, below 5.0.0
~4.17.21At least 4.17.21, below 4.18.0
4.17.21Exactly 4.17.21
^0.3.1At 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 dependencies for runtime code and devDependencies for development and build tools.
  • Version ranges allow releases; the lock file records exact installed versions.
  • Share both package files and rebuild node_modules.
  • npm ci installs the lock file without changing it and rejects mismatched package files.
  • npm update stays inside declared ranges; major upgrades need a deliberate choice.
  • Read audit reports and check behavior after dependency updates.
4

Check 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.

ToolMain question
ESLintDoes this code contain a pattern a rule flags?
PrettierDoes the code have the chosen layout?
Test runnerDoes 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 --write and 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 featurePurpose
BundlingCombine modules into fewer browser files
Tree shakingRemove code the build can prove unused
MinificationReduce size by removing layout and shortening names
Content hashesChange asset names when contents change, supporting caching
Code splittingKeep 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.
5

Place 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 actionWhat it does
tscChecks types and normally emits JavaScript
tsc --noEmitChecks types without writing output
Vite's TypeScript transformationRemoves type syntax for execution
Node.js type strippingRuns 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; TypeScript private is 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 folderRole
package.jsonCommands and direct dependency ranges
package-lock.jsonExact dependency tree
node_modules/Generated installed packages
eslint.config.jsLint rules and environments
.prettierrcFormatting options
vite.config.jsDevelopment and build options
tsconfig.jsonTypeScript 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

5 Walkthroughs

1

Build a small family of objects

18 steps
Create instances, guard their state, specialize behavior, and trace the shared methods that make the model work.
  1. 1
    IntroductionStart 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.

  2. 2
    ExampleInitialize 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);
    

    new creates a and runs the constructor with this referring to it. The two assignments give a its own width and height.

  3. 3
    ExampleCall shared behavior with separate data

    Add const b = new Rectangle(5, 2);. Both objects can call area, 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². Assigning b.width = 8 changes b only, so its next area is 16 cm² while a stays at 12 cm².

    The method does not need a separate copy of its code for every rectangle. The data it reads comes from this.

  4. 4
    TipKeep 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.width fails. The arrow wrapper keeps the original method call intact. a.area.bind(a) is another way to produce a callback tied to a.

  5. 5
    ChallengePredict a second call

    A new Rectangle(6, 2) is assigned to tile. The statement tile.width = 7 runs. What does tile.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.width does not update another instance. Sharing a method does not make the instance fields shared.

  6. 6
    ExplanationStore state behind a setter

    A thermostat can expose target while storing #target privately. 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.

  7. 7
    ExampleTrace 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.

  8. 8
    Key InsightChoose class or instance storage

    A field declared with static belongs to the class. Read the thermostat's lower limit as Thermostat.MIN, while t.target reads 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, this is the class receiving the call, so it does not provide one instance's target or dimensions.

  9. 9
    ChallengeUse the public path

    A subclass of Thermostat needs to raise its target from 20 to 24 °C. The parent declares #target and a validating public target setter. How should the subclass request the change, and what would direct this.#target access do?

    ---reveal---

    Assign this.target = 24 so the inherited setter validates and stores the value. The subclass cannot name the parent's private field directly; that use of #target is a syntax error. Inherited accessors still run in the class that declared the private state.

  10. 10
    ExampleGive related classes a parent

    A Shape constructor stores a name, and its describe() method calls this.area(). A circle specializes the calculation:

    class Circle extends Shape {
      constructor(radius) {
        super("Circle");
        this.radius = radius;
      }
      area() { return Math.PI * this.radius ** 2; }
    }
    

    super initializes the parent part before this.radius is used. A circle is a shape, so the inherited description fits its role.

  11. 11
    ExampleLet 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 its this.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.

  12. 12
    ExplanationExtend 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 call super.describe() and append the side length.

    super(...) in the constructor and super.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.

  13. 13
    ChallengeChoose a useful model

    A Report object must send messages through a Notifier. 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. Extending Notifier would claim that every report is itself a notifier just to reuse its code, which does not match the model.

  14. 14
    ExplanationFind a method through the chain

    A circle's own properties hold data such as its radius. When it lacks its own describe, JavaScript searches Circle.prototype, then Shape.prototype, where that method is stored.

    The ordinary chain continues through Object.prototype to null. If no object in the chain has a requested property, reading it yields undefined. instanceof Shape normally succeeds because Shape.prototype appears in the instance's chain.

  15. 15
    ExampleShadow an inherited setting

    Create settings with Object.create(defaults), where defaults.fontSize is 16. Assign settings.fontSize = 18.

    The assignment creates an own data property that shadows the inherited one. The default remains 16. Deleting settings.fontSize removes 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.

  16. 16
    ChallengeCheck ownership separately from access

    An object panel inherits theme: "light" from a plain prototype and has no own theme. What do panel.theme, Object.hasOwn(panel, "theme"), and "theme" in panel produce?

    ---reveal---

    They produce "light", false, and true. Reading and in can find inherited properties, while Object.hasOwn asks only about storage on panel itself. A successful read does not establish ownership. Object.keys(panel) also leaves out this inherited key.

  17. 17
    ChallengeConnect inheritance, privacy, and lookup

    Sensor declares #value, a public getter value, and a method describe() that reads this.value. IndoorSensor extends Sensor adds no members. Why can an indoor sensor call describe() 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 in Sensor can 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.

  18. 18
    RecapInspect 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 extends and constructor arguments before following overrides. Use ownership checks when you need stored keys, and follow prototypes when you need to explain inherited behavior.

2

Connect a program across module boundaries

13 steps
Export a class, load it in either environment, and delay optional work without losing clear dependencies.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionFollow one dependency graph
  2. 2
    ExampleExport the calculation
  3. 3
    ExampleExport and use the class
  4. 4
    ChallengeRead a changed export
  5. 5
    ExampleStart the files in a browser
  6. 6
    ExplanationResolve a package name
  7. 7
    ChallengeChoose the Node.js loader
  8. 8
    ExampleDelay an optional chart
  9. 9
    Key InsightIdentify what waits for setup
  10. 10
    ChallengeBreak an unnecessary cycle
  11. 11
    TipKeep work out of imports
  12. 12
    ChallengeCombine loading with a clear interface
  13. 13
    RecapTrace a module error by stage
3

Recreate an npm project on another computer

11 steps
Read scripts, choose dependency types, and use ranges with a lock file to reproduce the installation.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake the project reproducible
  2. 2
    ExampleCreate the package description
  3. 3
    ExampleRead the task before running it
  4. 4
    ChallengeIdentify a version change
  5. 5
    ExplanationRecord code and tools separately
  6. 6
    ExampleRead what a range permits
  7. 7
    ExplanationInstall the recorded tree
  8. 8
    ChallengeInterpret a blocked clean install
  9. 9
    TipPlan a deliberate update
  10. 10
    ChallengeCombine a range and a clean build
  11. 11
    RecapRead the two package records together
4

Check a project from edit to build

15 steps
Use linting, formatting, production preview, and tests to investigate different kinds of failures.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAsk each tool a different question
  2. 2
    ExplanationConfigure a useful lint report
  3. 3
    ExampleFind a typo before execution
  4. 4
    ChallengeChoose a formatting check
  5. 5
    ExampleRun development, build, and preview
  6. 6
    ExampleTrace a build optimization
  7. 7
    ConnectionConnect output to the source
  8. 8
    ChallengeChoose what to rebuild
  9. 9
    ExampleWrite an independent expectation
  10. 10
    Key InsightCompare contents and identity correctly
  11. 11
    ChallengeTest the exact boundary
  12. 12
    TipWait for an asynchronous test
  13. 13
    ExampleUse a sensible numerical tolerance
  14. 14
    ChallengeInvestigate a green build with a wrong result
  15. 15
    RecapRead failures as evidence
5

Fit types and components into a working project

10 steps
Distinguish checking from execution, then decide how project tools support a larger user interface.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionAdd a tool for a specific need
  2. 2
    ExampleCatch a mismatched call
  3. 3
    ExplanationSeparate transformation from checking
  4. 4
    ChallengeCheck data that arrives at runtime
  5. 5
    ExplanationPlace a component in the workflow
  6. 6
    ExampleRead the project task menu
  7. 7
    ChallengeChoose when a framework helps
  8. 8
    ChallengeFind the missing check
  9. 9
    TipAdopt checking gradually
  10. 10
    RecapFollow each tool to its evidence

JavaScript Classes and Modules practice questions

50 Practice Questions in 5 sets

Set 1: Read classes, imports, and project tasks10 Practice Questions

Question 1 of 10

A module default-exports a function and also exports LIMIT by name. How can one declaration import the function as calculate and the constant under its exported name?
  1. import { calculate, LIMIT } from "./calc.js";
  2. import calculate, LIMIT from "./calc.js";
  3. import calculate, { LIMIT } from "./calc.js";
  4. import { default, LIMIT } from "./calc.js";
Show answerHide answer
Answer: C
The default binding appears without braces, followed by the named import in braces. Requesting { 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

A class declares static created = 8;. Its instance is assigned to batch, and the class is named Batch. How is the stored count read?
  1. Batch.created
  2. new Batch().created
  3. Batch.prototype.created
  4. batch.constructor.prototype.created
Show answerHide answer
Answer: A
A static field belongs to the class, so the count is read as 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");
  1. It omits an own code field; assign this.code first.
  2. It inherits a constructor; remove the super(code) call.
  3. It passes a string to the parent; convert code first.
  4. It uses this too early; call super(code) first.
Show answerHide answer
Answer: D
A derived constructor cannot use 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

A module has only export default class Badge {}. What import binds that class to the local name Tag?
  1. import { Tag } from "./badge.js";
  2. import Tag from "./badge.js";
  3. import { Badge as Tag } from "./badge.js";
  4. import * as Tag from "./badge.js";
Show answerHide answer
Answer: B
A default import uses no braces and can choose the local name 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

A browser entry file contains an import declaration but its script tag has no type attribute. What HTML change makes it an ES module?
  1. Add defer alone to its script tag.
  2. Add async alone to its script tag.
  3. Add type="module" to its script tag.
  4. Add type="text/javascript" to its script tag.
Show answerHide answer
Answer: C
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

A project has "scripts": { "check": "eslint ." }. What command runs that named task?
  1. npm check
  2. npm install check
  3. node check
  4. npm run check
Show answerHide answer
Answer: D
Ordinary custom script names are run with 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

A project uses a formatter only during development. In which package.json section should its direct package entry normally be recorded?
  1. devDependencies
  2. dependencies
  3. scripts
  4. engines
Show answerHide answer
Answer: A
A formatter is a development tool, so its package belongs in 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

An ESLint run reports one warning and no errors under its ordinary exit settings. What does a successful exit establish about the code?
  1. No lint rule fired; the displayed warning is only formatting.
  2. Every behavior is correct; the warning cannot affect execution.
  3. No error-level rule failed; the warning still needs review.
  4. The warning was fixed; successful linting always changes files.
Show answerHide answer
Answer: C
Warnings are reported without failing ESLint by default, so success can coexist with a warning. It does not establish correct behavior: linting reads configured patterns without running the program. Automatic changes require a fixing command and a problem for which a fix exists.

Question 9 of 10

A function returns a newly created array containing "a" and "b". How should a Vitest test check those contents against a separate array literal?
  1. expect(result).toBe(["a", "b"]);
  2. expect(result).toEqual(["a", "b"]);
  3. expect(result === ["a", "b"]).toBe(true);
  4. expect(Object.is(result, ["a", "b"])).toBe(true);
Show answerHide answer
Answer: B
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

A function declared to accept numbers receives parsed server data. A Vite build succeeds, but the running program concatenates a string where a sum was intended. The project has no separate type-check task and no runtime data guard. What changes address both unchecked boundaries?
  1. Run a type checker and validate parsed values before calculation.
  2. Run the formatter and declare the parsed data as numeric.
  3. Run a type checker and trust every typed server response.
  4. Run a production preview and remove the type annotations.
Show answerHide answer
Answer: A
Vite can remove types without checking them, so a separate checker addresses source-level type mistakes. The actual server value still needs runtime validation because declarations do not inspect incoming data. Type checking alone cannot prove that the response obeys its declared shape.
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();
  1. It returns 2 because assigning a method preserves its receiver.
  2. It throws because the detached call has this equal to undefined.
  3. It returns undefined because the class field is private.
  4. It throws because instance methods cannot be assigned to variables.
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");
  1. true
  2. "cm"
  3. undefined
  4. false
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();
  1. 5
  2. 4
  3. undefined
  4. "Orbit"
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

What does import("./help.js") return immediately?
  1. The module's default-exported value itself.
  2. A fresh namespace with synchronous evaluation.
  3. A promise for the module namespace object.
  4. An array containing all exported values.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A stable package at 7.3.6 releases a compatible bug fix without a new feature. What next version matches semantic versioning?
  1. 7.4.0
  2. 7.3.7
  3. 8.0.0
  4. 7.3.6
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A project allows ~6.4.2. What is the newest stable version allowed among 6.4.9, 6.5.0, 7.0.0, and 6.4.1?
  1. 6.4.9
  2. 6.5.0
  3. 7.0.0
  4. 6.4.1
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

In a Vite project, what task writes the optimized production files to the usual output folder?
  1. npm run dev
  2. npm run preview
  3. npm install
  4. npm run build
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A test calls an async 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?
  1. Return immediately from the test and use toEqual(["x"]).
  2. Await loadItems() in an async test and keep toBe(["x"]).
  3. Await loadItems() in an async test and use toEqual(["x"]).
  4. Keep the unawaited call and check the promise with toBe(["x"]).
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A .ts program runs in Node.js through supported type stripping. Why does that successful run not establish that all its declared types are correct?
  1. Node.js converts every incoming value to its declared type.
  2. Type annotations remain active only until the first function call.
  3. Type stripping removes syntax without performing type checking.
  4. Node.js checks types only for code that uses browser APIs.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A team installs a component framework, ESLint, and a test runner in a Vite template. Only the original dev and build scripts exist; there is no lint configuration or test code. What work is still needed to support repeatable lint and behavior checks?
  1. Reinstall the tools globally and rely on the existing build script.
  2. Configure linting, write assertions, and expose the checks as scripts.
  3. Add script names alone and let the installed packages infer behavior.
  4. Move the tools into runtime dependencies and rebuild the template.
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];
  1. [3, 12]
  2. [1, 14]
  3. [12, 12]
  4. [3, 10]
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A class stores its only state in private field #level and provides a public getter on its prototype. It defines no toJSON method. What does JSON.stringify(new Gauge()) produce?
  1. "{\"level\":0}"
  2. "{\"#level\":0}"
  3. "{}"
  4. undefined
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")?
  1. It is discarded because the subclass constructor is absent.
  2. The default subclass constructor forwards it to Asset.
  3. It becomes a static field on the Photo class.
  4. It is stored only if Photo overrides every inherited method.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A module exports 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?
  1. It reads [1, 1] and can reassign its private imported copy.
  2. It reads [2, 2] and can reassign both imported bindings.
  3. It reads [1, 9] and cannot see changes to exported primitives.
  4. It reads [2, 9] and cannot reassign the imported count.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A module declares 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?
  1. Add defer while keeping the inline handler unchanged.
  2. Attach save with addEventListener from the module.
  3. Export save and expect HTML to import its name automatically.
  4. Rename the file to .mjs while keeping the inline handler.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A project's scripts contain "inspect": "eslint src". What command runs that script with ESLint's --fix argument?
  1. npm run inspect -- --fix
  2. npm run inspect --fix
  3. npm install inspect -- --fix
  4. npm inspect src --fix
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A shared ESLint configuration enforces quote style opposite to Prettier. Saved files flip quotes repeatedly. The code also returns an undefined name that triggers no-undef. What setup removes the formatting conflict while preserving the useful bug check?
  1. Disable style rules and no-undef, then use one formatter.
  2. Keep conflicting style rules and no-undef, then use one formatter.
  3. Disable conflicting style rules, keep no-undef, and use one formatter.
  4. Disable style rules, keep no-undef, and keep competing formatters.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

What kind of problem does Prettier's --write command directly change?
  1. The expected results stored in behavior assertions.
  2. Incorrect variable names inferred from runtime data.
  3. The published versions allowed by dependency ranges.
  4. Spacing and layout in supported source files.
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;
}
  1. Accept 0; reject "7"; reject NaN.
  2. Accept 12; reject "7"; reject NaN.
  3. Accept 0; accept "7"; reject NaN.
  4. Accept 0; reject "7"; accept NaN.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A static site uses npm tools to build browser output. What is the role of the dist folder in that workflow?
  1. Source configuration that must be edited before every build.
  2. The exact dependency record that replaces the npm lock file.
  3. Generated output that can be served by a suitable static host.
  4. Installed development tools that every browser downloads separately.
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

A setter accepts only finite numbers from 4 through 9. Its instance stores 6. The setter checks input before assigning and throws on failure. After an attempted assignment of "8" is caught, what value remains and why?
  1. 8 remains because the range check converts the string.
  2. 6 remains because private fields reject every setter call.
  3. 9 remains because the setter clamps invalid values to the maximum.
  4. 6 remains because the string fails the finite-number check.
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];
  1. [4, false, 4]
  2. [7, false, 4]
  3. [7, true, 7]
  4. [10, false, 4]
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];
  1. [undefined, 11]
  2. [15, 15]
  3. [11, 11]
  4. [undefined, undefined]
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A plain browser module imports "tiny-calendar". The package is installed locally, but the browser reports an unresolved specifier. What kind of missing setup explains this?
  1. A mapping or tool that resolves the bare name to browser-loadable code.
  2. A second installation that copies the package into global variables.
  3. A CommonJS type setting that makes the browser search node_modules.
  4. An async attribute that delays bare-name resolution until after parsing.
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?
  1. Both entries wait for config; construct with new plugin.default().
  2. Main waits for config; clock can proceed; construct with new plugin().
  3. Neither entry waits for config; construct with new plugin.Plugin().
  4. Main waits for config; clock can proceed; construct with new plugin.default().
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Matching package files declare ^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?
  1. 8.7.0
  2. 8.1.0
  3. 8.1.3
  4. The newest 9.x version.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A developer needs to inspect the existing Vite production output through a local HTTP server. What project task normally does that?
  1. npm run dev
  2. npm run preview
  3. npm run build
  4. npm init -y
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

An entry imports one pure helper from a module that exports two helpers and also performs required top-level registration. A Vite production build minifies output and includes a lazy-loaded feature. What output claim is justified?
  1. Unused helper code may be removed, required registration retained, and lazy code split.
  2. All unimported code, including registration, must disappear into one eager file.
  3. Both helpers must remain, registration is removed, and lazy code is inlined.
  4. Each source file must become one output file with all original names preserved.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

What command checks a TypeScript project without writing emitted JavaScript?
  1. vite build
  2. node app.ts
  3. prettier . --check
  4. tsc --noEmit
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

An application calls a date-formatting function when needed. A UI system instead calls the application's component code when rendering is required. What distinction do these examples illustrate?
  1. A library supplies callable helpers; a framework coordinates calls to application code.
  2. A framework supplies callable helpers; a library coordinates all application rendering.
  3. A library replaces modules; a framework replaces package installation and builds.
  4. A framework checks runtime types; a library checks types only during development.
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() + "?"; }
}
  1. "[A]!?"
  2. "[A]?"
  3. "[A!]?"
  4. "[A!?]"
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();
  1. 8
  2. 9
  3. 16
  4. 12
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?
  1. Both exports, with the default renamed to feature.
  2. The named ready export, without forwarding the default.
  3. Only the default, with named exports kept private.
  4. Neither export, because a barrel must evaluate them by calling functions.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A data module imports a UI module at top level to render immediately. The UI imports the data module and reads an uninitialized binding. Unit tests importing the data also try to access document in Node.js. What restructuring addresses both failures?
  1. Move event wiring to the entry module and keep shared data logic independent of the UI.
  2. Delay the same circular imports with a timer and add a global document name.
  3. Put all shared definitions in a larger barrel and keep import-time rendering.
  4. Catch every import error and rerun the unchanged UI module until it initializes.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What does "private": true in package.json directly prevent?
  1. Other modules from importing its exported functions.
  2. Browser users from reading the built source files.
  3. Accidental publication of the package through npm.
  4. npm from installing the project's development dependencies.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A project declares ^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?
  1. First 0.6.9, then 0.7.0.
  2. First 0.6.4, then 0.6.9.
  3. First 0.6.4, then 0.7.0.
  4. First 0.6.2, then 0.6.4.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A front-end source file reads a credential from a custom VITE_ variable. Why is that credential unsuitable for this location even after minification?
  1. The variable exists only in the editor and disappears before building.
  2. The prefix encrypts the value only while development mode is active.
  3. The value is protected unless a source map is also published.
  4. The exposed value is shipped to browser users and can be inspected.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A function should reject an empty name by throwing RangeError. What Vitest assertion lets the matcher observe the throw?
  1. expect(() => createName("")).toThrow(RangeError);
  2. expect(createName("")).toThrow(RangeError);
  3. expect(() => createName("")).toBe(RangeError);
  4. expect(createName).toEqual(new RangeError());
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A class uses TypeScript 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?
  1. Keep the private keyword, assert the response type, and repeat the formula in tests.
  2. Use a JavaScript # field, validate input, and test independent expected results.
  3. Use a JavaScript # field, trust the response annotation, and run formatting.
  4. Validate input, disable checking with any, and rely on a successful build.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A component project builds successfully, but it needs coordinated page URLs and server-rendered initial HTML. What wider project layer commonly provides both features around the component UI?
  1. A client-side router running only after JavaScript loads.
  2. A static fallback page without a server rendering step.
  3. A component state store without routing integration.
  4. A meta-framework with routing and server rendering.
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

88 Key Terms, each with its definition

A

Arrange–act–assert
Arrange–act–assert is a test structure that prepares inputs, executes the behavior, and checks the result against an expectation.
A test builds a rectangle, calls its area method, and then checks an independently calculated result.
Assertion
An assertion is a test statement that checks a specific expected property of a result and reports failure when that expectation is unmet.
An assertion can check that an invalid side length throws instead of creating a usable rectangle.

B

Bare specifier
A bare specifier names a package without a path prefix or full URL, requiring the environment or tooling to resolve that name.
Node.js can resolve an installed package name that a plain browser cannot locate without extra mapping.
Barrel file
A barrel file gathers re-exports from several modules into one import location, making their shared interface easier to reach.
A large barrel can hide a circular dependency when a module imports through its own folder's index.
Boundary test
A boundary test checks behavior at an exact limit or just beyond it, exposing mistakes in inclusive or exclusive comparisons.
A 10–30 °C target range needs checks at both endpoints and values just outside each permitted limit.
Bundler
A bundler follows a program's dependencies and combines or transforms them into output files suitable for serving to users.
A browser build can resolve installed packages and write optimized files into the generated dist folder.

C

Circular dependency
A circular dependency is an import cycle in which modules depend on each other directly or through other modules in the graph.
Moving a shared helper out of a data module and a rendering module into a third, independent module can remove the import cycle between them.
Class
A class is a template for creating objects with a common structure and shared behavior, using constructors, fields, and methods.
A Rectangle class can create several shapes whose area methods use their own widths and heights.
Clean install
A clean install recreates the recorded dependency tree with npm ci, requiring a matching lock file and replacing existing installed packages.
If the package description and lock disagree, a clean install fails instead of silently rewriting them.
Code splitting
Code splitting divides built code into separately loadable chunks so some features can be fetched later instead of at initial startup.
A dynamic import for a rarely used editor can give a build tool a boundary for later loading.
CommonJS
CommonJS is Node.js's original module system, using synchronous require calls and module.exports to provide values.
A .cjs file remains CommonJS even when the surrounding package explicitly selects ES modules.
Component
A component is a reusable UI part that groups markup and behavior, often with styles and state that affect its displayed output.
A navigation component can appear on several pages while keeping its interaction code in one place.
Composition
Composition builds behavior by giving an object other objects to delegate work to, expressing a has-a relationship between them.
A report holds a notifier and asks it to send a message without becoming a kind of notifier itself.
Constructor
A constructor is the special class method that runs when a new instance is created and sets up the object from supplied arguments.
A rectangle constructor stores its width and height so later method calls can calculate its area.
Constructor function
A constructor function is a function used with new to initialize objects, often paired with methods assigned to its prototype.
Older JavaScript may attach area to Rectangle.prototype after defining a constructor function.
Content hash
A content hash in an asset filename changes with the asset's contents, helping caches distinguish a new file from an older version.
A changed build asset gets a new hashed name so browsers can request it while caching unchanged files.

D

Default export
A default export provides a module's single default value, imported without braces under a local name chosen by the importer.
A default-exported class can be imported as Circle even if another file gives it a different local name.
Deferred script
A deferred script waits until HTML parsing finishes before execution; ordinary browser module scripts have deferred behavior by default.
A normal module entry can attach handlers to elements that appeared earlier in the completed HTML page.
Dependency
A dependency is a package the project relies on, with runtime packages normally recorded in the dependencies section of package.json.
A date-formatting library called by the application is a runtime dependency even if bundled for the browser.
Dependency audit
A dependency audit checks installed package versions against reports of known vulnerabilities, producing findings for review and remediation.
An audit finding should be read to determine whether affected code is used and which compatible fixes exist.
Dev server
A dev server serves a project locally while editing and can resolve imports, transform source, and update the page as files change.
A Vite dev server prints a local HTTP address through which the browser loads the working application.
Development dependency
A development dependency is a package needed to develop, check, or build a project, rather than to serve as its runtime library code.
A formatter or test runner is usually installed with -D so its version is recorded for the project.
Dynamic import
A dynamic import is an import() expression that loads a module on demand and returns a promise for its module namespace.
An async button handler can load an optional chart only when the user asks to display it.

E

Encapsulation
Encapsulation keeps internal state behind a controlled public interface whose operations maintain the rules for valid changes.
A thermostat lets callers request a target change while its setter decides whether the input is valid.
Enumerable property
An enumerable property has a flag that allows it to appear in enumeration operations that select enumerable keys.
Object.keys lists own enumerable string keys, while for...in can also include inherited ones.
ES module
An ES module is a JavaScript module using standard import and export syntax, with its own scope and strict execution rules.
A shape module can import geometry helpers without placing those helper names in the page's global scope.

F

Flat configuration
Flat configuration is ESLint's current JavaScript-based configuration format, combining configuration objects and rule settings.
An eslint.config.js module can export recommended checks plus the globals available in browser code.
Formatter
A formatter rewrites code layout consistently, arranging spaces, line breaks, indentation, and other supported style choices.
Prettier can normalize object spacing without deciding whether the object contains the correct values.
Framework
A framework provides an application structure and coordinates when user-supplied code runs within that structure.
A UI framework can call component code when changing state requires the displayed interface to update.

G

Getter
A getter is an accessor method that runs when a property is read, allowing a value to be calculated or exposed without call parentheses.
Reading thermostat.target can invoke a getter that returns the stored private temperature target.

H

Hot module replacement
Hot module replacement updates a changed module in a running application, often preserving supported state without a full page reload.
Editing a component may update its displayed behavior while leaving the current interface state intact.

I

Import map
An import map is browser configuration mapping module specifiers to URLs so module imports can use names the browser could not resolve alone.
A page places its import map before its module scripts to map a short package name to a server URL.
Inheritance
Inheritance lets a subclass build on a parent class's behavior, expressing an is-a relationship between the modeled kinds of objects.
A circle inherits a shape's description but supplies a calculation suitable for its own area.
Inherited property
An inherited property is found through an object's prototype chain rather than stored as an own property on that object.
A settings object can read a default theme while Object.hasOwn reports that it has no own theme key.
Instance
An instance is an individual object created from a class, with its own state and access to the behavior supplied by that class.
Two rectangle instances can share an area method while storing different dimensions in centimeters.
Instance field
An instance field is a property declared for each object created from a class, giving each instance its own stored value.
A counter field initialized to zero starts separately on each newly constructed counter object.
Interface
An interface describes an expected object shape for TypeScript checking, including the properties and methods that shape requires.
A shape interface can require a name and an area method without supplying the class implementation.

L

Library
A library provides reusable code that application code calls when needed, leaving the calling program in control of those requests.
An application calls a date-formatting helper when it needs to display a timestamp in the interface.
Linter
A linter analyzes source code against configured rules to report likely bugs or risky patterns without executing the program.
ESLint can detect a misspelled returned variable even when no test calls the function containing it.
Live binding
A live binding connects an imported name to the exporting module's current value, so exporter changes remain visible to importers.
When a module exports a count and an increment function, an importer reads the updated count through its imported name after increment runs.
Lock file
A lock file records the selected exact dependency tree so installations can reproduce versions without resolving new choices each time.
A build uses matching package records with npm ci to recreate the locked tree on another computer.

M

Meta-framework
A meta-framework adds wider application features around a UI ecosystem, commonly including routing, server rendering, and build setup.
A project may choose such a layer when it needs page navigation and server-rendered views together.
Method
A method is a function provided as part of an object or class interface, commonly operating on the object receiving the call.
Calling tile.area() lets the method read the dimensions of tile through the receiver this.
Method override
A method override is a subclass method that replaces an inherited method with the same name for calls selecting that subclass behavior.
A circle overrides area() while continuing to inherit the parent's shared description method.
Minification
Minification reduces generated code size by removing unnecessary layout and shortening supported names without changing intended behavior.
A production asset can have shorter local names even though its source used descriptive identifiers.
Mixin
A mixin is a reusable collection of methods copied into a class or prototype to add behavior without choosing another superclass.
A serialization mixin can supply helper methods to otherwise unrelated classes through their prototypes.
Module namespace
A module namespace is an object exposing a module's exports as properties, including default when a default export exists.
Awaiting a dynamic import gives a namespace from which the caller selects the exported constructor.
Module specifier
A module specifier is the string identifying the module requested by an import, such as a relative path, a URL, or a package name.
The specifier "../utils.js" asks the loader for a module one folder above the importing file.

N

Named export
A named export exposes a declaration under a particular exported name that importers request using braces in an import declaration.
A limits module exports MAX, and an importer can request it directly or rename it locally.
npm
npm is the package manager provided with Node.js, consisting of a command-line tool and an associated public package registry.
A project uses npm to install dependencies and run the commands recorded in its package scripts.
npm script
An npm script is a named command in package.json that gives a project task a repeatable entry point through the npm command line.
The lint script can run the project's locally installed tool when a teammate runs npm run lint.
npx
npx is a command runner that can use an installed package executable or offer to download and run a package command temporarily.
A developer can try a static server command without adding it as a permanent dependency of the project.

O

Own property
An own property is stored directly on an object, unlike an inherited property available through one of its prototype links.
A rectangle's width can be an own property even though its ordinary area method is inherited.

P

Package
A package is a folder described by a package.json file, which records its identity and can describe scripts and dependencies.
A local application can be an npm package even when its private flag prevents registry publication.
Polymorphism
Polymorphism lets the same method call select different implementations for different receiving objects through a shared interface.
One loop calls describe() on circles and rectangles, and each object supplies the appropriate area.
Private field
A private field is class-declared storage with a name beginning #, directly accessible only within the class that declares the name.
A child class uses its parent's public getter instead of directly naming a private target field that the parent declares.
Private method
A private method is internal class behavior named with #, keeping helper operations outside the public interface of the object.
A class can call this.#check(value) to validate input without exposing that helper to outside code.
Prototype
A prototype is an object, or null, linked to another object and searched when that object lacks a requested own property.
An instance can find a shared area method on its class prototype without storing its own method copy.
Prototype chain
A prototype chain is the sequence of links followed during property lookup, ending at null when no further prototype exists.
A circle can find its description on Shape.prototype after lookup passes through Circle.prototype.
Public interface
A public interface is the set of accessible operations through which other code uses an object without depending on its internal storage.
Callers can set a thermostat target even if the class later changes how it stores that target.

R

Re-export
A re-export forwards another module's exports through the current module, allowing a file to collect a public interface.
A folder's index can forward named helpers; an export-star declaration does not forward their default.
Receiver
A receiver is the object on which a method is called, normally supplying the value of this for an ordinary method call.
In box.open(), the receiver is box; detaching open does not permanently bind that receiver.
Rule severity
A rule severity determines whether a lint rule is disabled, reports a warning, or reports an error that normally fails the command.
An unused variable warning can appear in a successful run while an undefined-name error makes it fail.
Runtime validation
Runtime validation checks actual values while the program runs, especially data arriving from external sources that types cannot inspect.
Parsed network data needs a guard before a declared numeric field is trusted in a calculation.

S

Semantic versioning
Semantic versioning labels releases as MAJOR.MINOR.PATCH to distinguish breaking changes, compatible features, and compatible fixes.
A stable release adding a compatible feature moves from 2.4.1 to 2.5.0 instead of a new major version.
Setter
A setter is an accessor method that runs when a value is assigned to a property, allowing validation before storage changes.
A target setter can reject an out-of-range temperature before assigning it to a private field.
Shadowing
Shadowing occurs when an own property hides an inherited property with the same name during ordinary property lookup.
Deleting an object's own font size can reveal the unchanged default size on its prototype again.
Side effect
A side effect is observable work beyond returning a value, such as changing shared state, updating the page, or registering behavior.
A module that changes the page at import time is harder to test without a browser environment.
Source map
A source map connects positions in generated code to positions in original source files, making transformed output easier to debug.
A stack trace can point to the original function when the browser has the matching map available.
State
State is the data representing an interface's current condition, whose changes can cause the UI system to update what users see.
Changing the selected task can update several views that all depend on that shared state.
Static analysis
Static analysis inspects code without running it, using its written structure and configured rules to identify potential problems.
An undefined-name diagnostic can appear in the editor before the application executes that line.
Static factory
A static factory is a class-level method that creates and returns instances through a name describing a particular construction choice.
A rectangle class can offer Rectangle.square(5) to create an instance with equal side lengths.
Static initialization block
A static initialization block is a block inside a class that runs once during class definition to perform class-level setup.
A class can fill a shared lookup table in a static block without repeating that setup for every object.
Static member
A static member is a field or method belonging to the class itself, accessed through the class instead of an individual instance.
A shared construction count is read from the class while each object retains its own instance data.
Subclass
A subclass is a class declared with extends that inherits from one superclass and can add members or specialize inherited behavior.
A square subclass can pass the same side length twice to its rectangle parent's constructor.
Superclass
A superclass is the parent class from which a subclass inherits, supplying initialization and behavior available to derived instances.
The shape superclass stores the common name used when its subclasses produce their descriptions.

T

Test runner
A test runner discovers and executes tests, reports results, and normally returns a failure exit code when a test fails.
A project script can invoke the Node.js runner or Vitest before changes are shared with a teammate.
Test-driven development
Test-driven development begins with a failing behavior test, adds enough implementation to pass, and then improves code while tests stay green.
A new validation rule can first be expressed as a failing test before its guard is implemented.
Tolerance
A tolerance is an allowed numerical difference used to compare approximate results without accepting errors beyond the chosen bound.
A floating-point test can permit a tiny rounding difference while rejecting an incorrect area formula.
Top-level await
Top-level await lets an ES module wait outside a function during evaluation, delaying modules that depend on that evaluation.
A settings module can finish fetching configuration before its dependent entry module starts its body.
Transitive dependency
A transitive dependency is a package required indirectly because one of the project's dependencies, or its dependencies, needs it.
The lock file records nested packages even when the application never imports those packages directly.
Tree shaking
Tree shaking removes code that build analysis can prove unused and safe to omit, while retaining side effects that must execute.
An unused pure helper can disappear from output even when another helper from the same module remains.
Type annotation
A type annotation states the expected kind or shape of a value in source code, supplying information for static checking.
A numeric parameter annotation helps an editor reject a mismatched call before the function runs.
Type definition
A type definition describes the types of existing code for tools, often provided through .d.ts files or separate definition packages.
A JavaScript library can supply editor completion and checking information without rewriting its runtime code.
Type stripping
Type stripping removes supported type syntax to allow execution without itself checking whether the types are used consistently.
A TypeScript file can execute through Node.js stripping even when a separate checker would report an error.
TypeScript
TypeScript adds static type syntax and checking to JavaScript so incompatible uses of values can be detected before execution.
The checker can report a string passed to a parameter declared numeric even if JavaScript could coerce it.

U

Unit test
A unit test runs a small part of a program with controlled inputs and checks that its observed behavior matches an expected result.
A rectangle test can check a known area without loading the page that displays the rectangle.

V

Version range
A version range describes the releases a dependency entry permits, which can be broader than the exact version currently installed.
A caret range can allow a newer minor release while a clean install still follows an older locked choice.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started