Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ES2015+ ~28 min· topic 5 of 8

Topic 7.5

Classes

In one line

A class is a cleaner way to write a constructor and its prototype methods in one block, with fields, static members, getters and setters; under the hood it builds the same prototype chain, but adds safety rules such as strict mode and refusing to run without new.

Think of it like this

A form you fill in at a bank. The blank form is the class: it lists the boxes every account has (name, balance) and the rules ("deposits must be positive"). Each filled-in form is an instance: same boxes, different answers. A note printed at the top of the form, like "Branch opened in 1998", belongs to the form itself, not to any customer: that's a static member.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Class
A blueprint for objects, written with class Name { ... }. It is a special kind of function.
constructor
The method in a class that runs once for every new instance, to set it up.
Public field
A property declared in the class body (count = 0;) that every instance gets its own copy of.
Static member
A property or method on the class itself (Counter.created), not on instances.
Factory method
A static method that creates and returns an instance in a special way, like Temperature.fromFahrenheit(32).
Getter / setter
Methods marked get or set that run when a property is read or assigned.
Syntax sugar
A nicer way to write something the language could already do.
Enumerable
Shows up when looping over keys (for...in, Object.keys). Class methods are not enumerable.

Step by step

01Declare a class

The class name starts with a capital. The constructor receives the arguments from new and stores them on this.

point.jswhole filejavascript
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}
const a = new Point(3, 4); // Point { x: 3, y: 4 }

02Add methods and a getter

Methods go straight in the body, without the word function and without commas. A getter looks like a property from outside.

point.jswhole filejavascript
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  distanceTo(other) {
    return Math.hypot(other.x - this.x, other.y - this.y);
  }
  get length() {
    return Math.hypot(this.x, this.y);
  }
}
new Point(3, 4).length; // 5 (no brackets: it is a getter)

03Add fields and static members

count = 0; gives each counter its own starting value. static created = 0; is one number shared by the class, read as Counter.created.

counter.jswhole filejavascript
class Counter {
  count = 0;
  static created = 0;
  constructor() {
    Counter.created++;
  }
  increment() {
    this.count++;
  }
}

04See what the class built

The class is the constructor function. Methods and getters are on its prototype; fields are on each instance; static members are on the class itself.

See what the class builtdiagram
Rendering diagram…

05Know the extra rules

A class can't be called without new, can't be used before its declaration line, and its code is always strict. These rules turn silent bugs from Topic 7.4 into clear errors.

terminal
$ Point(1, 2)
── expected output ──
Uncaught TypeError: Class constructor Point cannot be invoked without 'new'

Try it yourself

  1. 1

    Add a static factory

    In the first example, add static fromArray([x, y]) { return new Point(x, y); } and log Point.fromArray([6, 8]).length. Predict the number first.

  2. 2

    Swap a field for a method

    In the last example, change handleClick = () => { ... }; into a normal method handleClick() { ... } (keep the body). Predict what happens on the first click, then check the console.

  3. 3

    Make a field depend on another

    In the second example, add a field label = "Counter #" + Counter.created; above the constructor. Predict c2.label. (Fields run before the constructor body.)

Code & diagrams

A class with methods and a getter New tab

`length` is read without brackets because it is a getter. Joining the point with a string calls `toString()` automatically.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Point { x: 3, y: 4 }
5 5 (3, 4)
function [class Point]
Fields and static members ES2022+ New tab

Fields appear on each instance in the order they were declared. Static members live on the class, so instances can't see them.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Counter { count: 2, step: 1 } Counter { count: 5, step: 5 }
created: 2 | on an instance: undefined
[ 2, 5 ]
Getters, setters and a static factory New tab

Only `celsius` is stored. `fahrenheit` is computed by the getter and converted back by the setter, which also checks the value.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

77
100 Temperature { celsius: 100 }
Temperature { celsius: 0 }
TypeError: fahrenheit must be a number
Proof that a class is a function with a prototype New tab

The method `show` is on the prototype but hidden from `for...in`. The last line shows a class refusing a call without `new`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

true
[ 'constructor', 'show' ]
enumerable keys on the prototype: []
for...in sees x
Uncaught TypeError: Class constructor Point cannot be invoked without 'new'
An arrow-function field keeps this for event handlers New tab

The arrow is created per instance (a field), so its `this` is that instance. The cost: each instance has its own copy instead of one shared prototype method.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

on: true
on: false
own copy per instance? true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a class before its declaration

Write const p = new Point(1, 2); above class Point { ... }.

terminal
$ Console
── what you'll see ──
Uncaught ReferenceError: Cannot access 'Point' before initialization

Break #2

Put commas between methods

Write class A { one() {}, two() {} } (with a comma, as in an object literal).

terminal
$ Console
── what you'll see ──
Uncaught SyntaxError: Unexpected token ','

Break #3

Pass a class method as a callback

Inside a class, write button.addEventListener("click", this.save); where save() { this.saved = true; } is a normal method.

terminal
$ Console
── what you'll see ──
No error, but the object's saved property never changes (the property is set on the button instead).

Myth vs fact

Myth

JavaScript classes work like Java or C# classes.

Fact

They are a syntax over prototypes: instances link to a prototype object at runtime, and you can still change prototypes, add methods later, or create instances with Object.create. There are no interfaces, overloads or compile-time types.

Myth

Class methods are automatically bound to the instance.

Fact

They are ordinary prototype methods with dynamic this. Only arrow-function fields are bound per instance.

Myth

A field like count = 0; lives on the prototype.

Fact

Fields are created on each instance (they show in Object.keys and JSON). Only methods, getters and setters are on the prototype.

Myth

Classes are always better than plain objects and functions.

Fact

They shine when you have many instances sharing behaviour. For one-off objects or pure data, a literal or a few functions is simpler (Topic 7.8).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Fields are defined with **[[DefineOwnProperty]]** semantics, not assignment: a field named x in a subclass replaces an inherited accessor x instead of calling its setter. TypeScript's old useDefineForClassFields: false used assignment, which is why switching it on can change behaviour.

  • ▸

    Arrow-function fields cost one closure per instance and can't be overridden through super.method() or spied on via the prototype in tests. For classes with many instances, prefer prototype methods and bind only where a callback is needed.

  • ▸

    Class bodies can use computed names ([Symbol.iterator]() { ... }, ["get" + name]() {}), generator methods (*items() {}) and async methods. Implementing [Symbol.iterator] makes instances work with for...of and spread.

  • ▸

    A class has an inner binding of its own name that can't be reassigned inside the class body, and class X {} at the top level of a script does not create window.X (like let). Browser features such as custom elements (class MyCard extends HTMLElement) require real classes, and extends is covered in Topic 7.7.

Remember this

  1. 1

    A class (ES2015) groups everything about one kind of object in one place. class Point { ... } declares it, and new Point(3, 4) creates an instance. The special method **constructor(x, y)** runs once for each new instance, with this set to that instance, exactly like a constructor function (Topic 7.4). If you leave it out, an empty one is used.

  2. 2

    Methods written in the class body, like toString() { ... }, are put on Point.prototype, so all instances share them through the prototype chain (Topic 7.3). Unlike properties you add by hand, class methods are non-enumerable: they don't show in for...in or Object.keys. Methods are separated by nothing: no commas, unlike an object literal.

  3. 3

    Public fields (ES2022) declare instance properties right in the body: count = 0; or step; (starts as undefined). Each instance gets its own copy, created just before the constructor body runs (in a base class), in the order written. A field can use this and earlier fields: total = this.price * 2;. Fields are good for defaults; the constructor is for values that come from arguments.

  4. 4

    **static members belong to the class itself**, not to instances: static created = 0; and static compare(a, b) { ... } are used as Counter.created and Counter.compare(...). Use them for helpers and factory methods (alternative ways to create an instance, like Temperature.fromFahrenheit(32)), and for counters or settings shared by the whole class. static { ... } blocks (ES2022) run setup code once, when the class is defined.

  5. 5

    Getters and setters (get fahrenheit() { ... }, set fahrenheit(f) { ... }, from Topic 6.7) look like properties from outside but run code: t.fahrenheit calls the getter, t.fahrenheit = 212 calls the setter. Use them for values computed from other fields and for checking a value before storing it. Like methods, they live on the prototype.

  6. 6

    A class is mostly syntax sugar (a nicer way to write the same thing) over constructor functions: typeof Point is "function", and instances are linked to Point.prototype. But it adds real rules: the body is always in strict mode; calling Point() without new throws; methods can't be used with new; and a class declaration is not usable before its line (it is in the temporal dead zone, Topic 3.5, unlike a function declaration). Classes can also be written as expressions: const Point = class { ... };.

Explain it without notes

01

What does the class syntax actually create? Is it only syntax sugar?

02

Where do constructor properties, fields, methods, getters and static members end up?

03

When would you use a static method instead of an instance method?

04

Why might you write a method as an arrow-function field, and what does it cost?

Practice

01

Write a class Rectangle with width and height from the constructor, a getter area, a method scale(factor) that returns a new Rectangle, and a static square(size). Print new Rectangle(2, 3).area and Rectangle.square(4).scale(2).area.

02

Write a class Ticket that gives every new ticket an id from a static counter (1, 2, 3, ...). Create three tickets and print their ids.

03

Write a class Username with a setter value that trims the text and throws a RangeError if it's shorter than 3 characters, and a getter that returns it. Show a valid and an invalid assignment.

Trade-offs

  • ↔

    Classes give a familiar, organised structure and safety rules, but encourage putting everything into objects with this, which brings back the lost-this problems; plain functions avoid this but don't share methods automatically.

  • ↔

    Arrow-function fields make callbacks safe, but cost memory per instance and can't be overridden through super; prototype methods are cheaper but need bind or a wrapper when passed around.

Done when you can

  • I can write a class with a constructor, methods, a getter and a setter.

  • I can use public fields and static members and say where each one lives.

  • I can explain how a class maps onto a constructor function and its prototype.

  • I know the extra rules: strict mode, new required, TDZ, no commas.

  • I can keep this for callbacks with an arrow field or bind.