Skip to content
aviral gupta

// I2.2 · ~34 min · Intermediate

Classes and constructors

After this lesson you write a class with a constructor and methods, create objects with new, and know where its methods live.

Lesson 2 of 5 in I2 Objects, prototypes and classes

You will be able to

  • Write a class with a constructor and methods, and create instances of it with new
  • Explain what a class is under the hood: a function whose prototype holds the methods
  • Use this in a constructor and methods, and fix a missing new, an early use and a lost this
  1. Warm-up · Activity 1 of 7

    Warm-up from lesson I2.1. What does this print?

    const proto = {hello() { return `hi ${this.name}`; }};
    const a = Object.create(proto);
    a.name = "Ada";
    console.log(a.hello(), Object.hasOwn(a, "hello"));
  2. Predict · Activity 2 of 7

    Predict before you read on. What does this print?

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    const p = new Point(1, 2);
    console.log(typeof Point, Object.keys(p).join(","));
  3. Practice · Activity 3 of 7

    Fill in the operator that creates a Dog.

    class Dog {
      constructor(name) {
        this.name = name;
      }
    }
    const rex = ____ Dog("Rex");
    const rex =Dog("Rex");
  4. Practice · Activity 4 of 7

    Put the steps of new Book("Momo", 304) in order.

    1. 1.Its prototype is set to Book.prototype
    2. 2.new gives back the object, now with title and pages
    3. 3.A new, empty object is created
    4. 4.The constructor runs with this set to the new object
  5. Practice · Activity 5 of 7

    Two dogs. What does this print?

    class Dog {
      constructor(name) {
        this.name = name;
      }
      bark() {
        return `${this.name}: woof`;
      }
    }
    const a = new Dog("Rex");
    const b = new Dog("Bello");
    console.log(a.bark === b.bark, b.bark());
  6. Brain teaser · Activity 6 of 7

    Brain teaser. This constructor returns an object of its own. What does this print?

    class Strange {
      constructor() {
        this.kind = "instance";
        return {kind: "plain object"};
      }
    }
    const s = new Strange();
    console.log(s.kind, s instanceof Strange);
  7. Apply · Activity 7 of 7

    Mini-task: write a class Rectangle whose constructor takes width and height. Give it the methods area(), perimeter() and describe(), which returns a text such as "2 x 3: area 6, perimeter 10" by calling the other two. Make two rectangles, print both descriptions, and show that they share area, that they are instances of Rectangle, and what typeof Rectangle is.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A Book class, then a look under the hood

The class Book stores a title and a page count in its constructor and has two methods. Two books are made with new. The program prints them, then checks what the class really is: a function, whose prototype holds describe and isLong for every book. At the end it calls Book without new.

main.js

class Book {
  constructor(title, pages) {
    this.title = title;
    this.pages = pages;
  }

  describe() {
    return `${this.title} (${this.pages} pages)`;
  }

  isLong() {
    return this.pages > 300;
  }
}

const novel = new Book("Momo", 304);
const guide = new Book("JS Pocket", 120);

console.log(novel.describe(), novel.isLong());
console.log(guide.describe(), guide.isLong());
console.log(novel);

// Under the hood: a function, and methods on its prototype.
console.log(typeof Book);
console.log(Object.getPrototypeOf(novel) === Book.prototype);
console.log(novel.describe === guide.describe, Object.hasOwn(novel, "describe"));
console.log(novel instanceof Book, novel instanceof Object);

try {
  Book("Oops", 1); // forgot new
} catch (error) {
  console.log(`${error.name}: ${error.message}`);
}

Run it with

node main.js

Output

Momo (304 pages) true
JS Pocket (120 pages) false
Book { title: 'Momo', pages: 304 }
function
true
true false
true true
TypeError: Class constructor Book cannot be invoked without 'new'
  • Node.js shows the object with its class name, Book, and only its own data: title and pages.
  • describe is the same function for both books, and it is not an own property.
  • novel is an instance of Book and of Object, because Object.prototype is further up its chain.
  • Without new, the class refuses to run at all.
Change it and run it

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Exercises

Exercise 1 of 2

Store what the constructor gets

Temperature should store its celsius value so that toFahrenheit() can use it: new Temperature(100).toFahrenheit() is 212. The starter’s constructor only puts the value into a local variable, so the method reads this.celsius as undefined and returns NaN. Fix the constructor.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: toFahrenheit gives NaN. What is this.celsius inside it?

  2. Hint 2

    A const inside the constructor disappears when the constructor ends. Data an object keeps goes on this.

  3. Hint 3

    this.celsius = celsius;

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }

  toFahrenheit() {
    return (this.celsius * 9) / 5 + 32;
  }
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

export class Temperature {
  constructor(celsius) {
    const value = celsius;
  }

  toFahrenheit() {
    return (this.celsius * 9) / 5 + 32;
  }
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {Temperature} from './main.js';

test('100 degrees Celsius are 212 Fahrenheit', () => {
  const got = new Temperature(100).toFahrenheit();
  assert.equal(got, 212, `new Temperature(100).toFahrenheit() gave ${got}`);
});

test('The celsius value is an own property', () => {
  const t = new Temperature(-40);
  assert.equal(t.celsius, -40, `t.celsius is ${t.celsius}`);
  assert.equal(Object.hasOwn(t, 'celsius'), true, 'celsius should be set on this in the constructor');
});

test('toFahrenheit stays on the prototype', () => {
  const t = new Temperature(0);
  assert.equal(Object.hasOwn(t, 'toFahrenheit'), false, 'toFahrenheit should be a method of the class, not an own property');
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

Each basket its own items

Basket has add(name, price), which returns the basket so calls can be chained, and total(). The starter keeps every item in one array ITEMS outside the class, so all baskets share it. Give each basket its own array: create this.items in a constructor and use it in both methods. Two baskets must not see each other’s items.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Run the tests: a new basket already has items. Where are they stored?

  2. Hint 2

    Data that belongs to one object goes on this, set in the constructor.

  3. Hint 3

    constructor() { this.items = []; } and then this.items.push(…) and this.items.reduce(…).

Show a solution

One way to solve it. Yours can look different and still pass the checks.

export class Basket {
  constructor() {
    this.items = [];
  }

  add(name, price) {
    this.items.push({ name, price });
    return this;
  }

  total() {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
}
Run it on your computer

Install ECMAScript 2026 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

const ITEMS = [];

export class Basket {
  add(name, price) {
    ITEMS.push({ name, price });
    return this;
  }

  total() {
    return ITEMS.reduce((sum, item) => sum + item.price, 0);
  }
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {Basket} from './main.js';

test('add can be chained and total adds the prices', () => {
  const basket = new Basket().add('tea', 3).add('cake', 4);
  assert.equal(basket.total(), 7, `the total is ${basket.total()}`);
});

test('A new basket is empty', () => {
  new Basket().add('tea', 3);
  const fresh = new Basket();
  assert.equal(fresh.total(), 0, `a new basket has the total ${fresh.total()}`);
});

test('Two baskets keep their own items', () => {
  const a = new Basket();
  const b = new Basket();
  a.add('bread', 2);
  b.add('milk', 1);
  assert.deepEqual([a.total(), b.total()], [2, 1], `the totals are ${a.total()} and ${b.total()}`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

Calling a class without new

class Book {
  constructor(title) {
    this.title = title;
  }
}
const book = Book("Momo");
console.log(book.title);

What Node.js prints

TypeError: Class constructor Book cannot be invoked without 'new'

Why, and the fix

A class can only be used with new, which makes the object that this points to. Called like a plain function, it throws at once. Write const book = new Book("Momo");.

Using a class before its declaration

const p = new Point(1, 2);
console.log(p.x);

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

What Node.js prints

ReferenceError: Cannot access 'Point' before initialization

Why, and the fix

A function declaration can be called above the line that declares it; a class cannot. Until its declaration has run, the class name is in the temporal dead zone, like a let or const. Move the class above the code that uses it, or into a module you import.

Calling a method that lost its this

class Dog {
  constructor(name) {
    this.name = name;
  }
  bark() {
    return `${this.name}: woof`;
  }
}
const rex = new Dog("Rex");
const bark = rex.bark;
console.log(bark());

What Node.js prints

TypeError: Cannot read properties of undefined (reading 'name')

Why, and the fix

this is set by the call, not by where the method came from. bark() has no object before the dot, and class code is strict, so this is undefined. Call it as rex.bark(), or, when you must pass it on, as a callback, wrap it: () => rex.bark().

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

A class bundles a constructor and methods

class Book { … } describes a kind of object. new Book("Momo", 304) makes a new, empty object, runs the constructor with this set to that object, and returns it. The constructor stores each object’s own data, such as this.title = title. Methods are written in the class body without the word function and use this to reach the object they were called on. A class without a constructor gets an empty default one, and a class may have only one constructor.

Under the hood: a function and its prototype

A class is a special function: typeof Book is "function". Its methods are not copied into every book. They are stored once on Book.prototype, and every object made with new Book links to Book.prototype, the same chain as in lesson I2.1. So Object.getPrototypeOf(novel) === Book.prototype, and two books share one describe. novel instanceof Book checks whether Book.prototype appears anywhere in novel’s chain. Only the properties set on this in the constructor are own properties of the object.

The rules classes add

A class must be called with new; Book("Momo") throws a TypeError. Unlike a function declaration, a class cannot be used before the line that declares it: that throws a ReferenceError. The code in a class body runs in strict mode, so when you take a method off its object, const f = novel.describe, and call f() alone, this is undefined and reading this.title throws. Call methods with the dot, novel.describe(), or wrap them: () => novel.describe().

Sources

Last reviewed October 5, 2026