Learn by reading through in order

Class Basics — Defining a Class and Creating Instances

Learn how class lets you create many objects with the same shape: creating instances with constructor and new, writing methods that use this, and knowing when an object literal is enough.

Members, products, coupons: data with the same fields and the same calculations shows up again and again in an app. If you write each one as an object literal, you end up copying the fields and the calculation for every single item.

This article covers class, which keeps the shape of your data and the code that works on it in one place, and shows how to create objects from it one at a time with new.

Defining the Shape of Your Data in One Place — class and new

Say you're handling two kinds of discount coupons. If you write { code: "WELCOME", discount: 500 } once per coupon and mistype it as discont in just one of them, no error is thrown; that one coupon's discount simply becomes undefined. Because the key names are repeated in every coupon, you're left hunting through all of them for the typo.

You write a class (syntax that defines a kind of object you create with new) as class Coupon { ... } and put a constructor (the part that new calls) inside it, with no function keyword.

Inside the constructor, this is the new instance (an object created from a class), and new returns it even though there's no return.

// Define the shape of a coupon in one place with a class
class Coupon {
  constructor(code, discount) {
    this.code = code;   // Left: the instance's code. Right: the argument passed in
    this.discount = discount;
  }
}

// Create coupons one at a time with new
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);
console.log(welcome.code);                // WELCOME
console.log(summer.discount);             // 1000
console.log(welcome instanceof Coupon);   // true

// Calling it without new throws an error
// Coupon("SPRING", 300);                 // TypeError
What Happens With and Without new
Definition ofclass Couponnew Coupon("WELCOME", 500)new Coupon("SUMMER", 1000)Coupon("SPRING", 300)Set this.codeto "WELCOME"Set this.codeto "SUMMER"No new: neverenters constructorwelcome getsthe instancesummer getsanother instanceThrows aTypeErrorpasspass
The arguments are written to this inside the constructor. Without new, the call throws before it even enters the constructor.

The key names appear in just one place, inside the constructor, so there's only one place where a typo like discont could be, and only one place to fix it. In the right-hand column, the console shows TypeError: Class constructor Coupon cannot be invoked without 'new'.

Create product data with a class. productName and productPrice are already declared.

① Define Product so that each instance holds a product name and a price.

② Pass the two variables to create mouse.

③ Display mouse in the form "Name: price yen".

④ Call Product without new inside a try block, and display the error's name.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

Calculating with an Instance's Values — Methods and this

If you calculate the discounted amount with total - welcome.discount on both the cart screen and the order confirmation screen, you have two places to fix every time the pricing rule changes. Even if you move it into a function, the fields are in the class while the calculation lives somewhere else.

A method (a function that belongs to an object and is called with a dot, as in welcome.apply(3000)) goes inside the class's braces as apply(total) { ... }, again with no function keyword. Inside a method, this refers to the instance written to the left of the dot, and this.discount reads its value at the moment of the call.

class Coupon {
  constructor(code, discount) {
    this.code = code;
    this.discount = discount;
  }

  // Subtract this coupon's discount from the total and return the result
  apply(total) {
    return total - this.discount;   // this is the instance to the left of the dot
  }
}

// Create two kinds of coupons
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);

// The same apply gives a different result depending on the instance left of the dot
console.log(welcome.apply(3000));   // 2500
console.log(summer.apply(3000));    // 2000
The Left Side of the Dot Becomes this
welcome.apply(3000)this iswelcomethis.discountis 500Returns 2500summer.apply(3000)this issummerthis.discountis 1000Returns 2000
this is the variable written to the left of apply. Even with the same argument, a different this gives a different result.
Where the constructor's Arguments Exist
Inside class Coupon
Inside constructor(code, discount)
  • Arguments code / discount — usable only inside these braces
  • this.discount = discount — stores the value on the instance
Inside apply(total)
  • this.discount — reads the value stored on the instance
  • total - discount — the name isn't visible, so ReferenceError
The name discount isn't visible outside the constructor's braces. apply reads the value that was stored on this instead.

If you forget this. inside apply and write total - discount, JavaScript looks for the name discount starting from the innermost braces and working outward. So the result depends on whether a variable with the same name exists outside the class.

A Missing this. Doesn't Always Throw an Error

If a variable with the same name exists outside the class, such as const discount = 300;, then total - discount with the missing this. doesn't throw a ReferenceError. It quietly uses that 300 instead. If every coupon returns the same amount no matter which one you call it on, suspect a missing this..

Calculate a keyboard's tax-inclusive price with a method on Product. Product and keyboard are already declared.

① Add a method taxIncluded to Product that returns the price including tax (10% tax, rounded down).

② Display keyboard's name and price including tax in the form "Name: N yen incl. tax".

③ Change keyboard's price to 9800.

④ Display it again in the same form as ②.

JavaScript / TypeScript Editor

Run code to see output

Each Instance Keeps Its Own Values — Multiple Instances

Each time a coupon is used, its remaining count goes down by one. If you keep the remaining counts outside the class in an object like { WELCOME: 3, SUMMER: 3 }, every screen that uses a coupon needs its own code to look up the count by code and write the new value back.

Inside a method, you can also assign to a property of this, as in this.remaining = this.remaining - 1. The right side reads the current count, and the result, one less, is written back to the same instance's remaining. The count starts at the 3 you passed to new and goes down by one with each call.

class Coupon {
  constructor(code, remaining) {
    this.code = code;
    this.remaining = remaining;   // How many uses are left
  }

  // Lower the count of the instance to the left of the dot by one
  use() {
    this.remaining = this.remaining - 1;
  }
}

// Create two coupons with the same count, then use them a different number of times
const welcome = new Coupon("WELCOME", 3);
const summer = new Coupon("SUMMER", 3);
welcome.use();
welcome.use();
summer.use();
console.log(welcome.remaining);   // 1
console.log(summer.remaining);    // 2
use() Only Lowers the Count Left of the Dot
welcome.use()twicethis iswelcomeremaining goes3 → 2 → 1summer isuntouchedsummer.use()oncethis issummerremaining goes3 → 2welcome isuntouched
welcome's use() and summer's use() never touch each other's counts. Only the instance written to the left of use() loses a use.

You can store instances in an array as well as in variables. When you call a method on an element, as in coupons[0].use(), this refers to that element. However many coupons you have, the code that lowers the count is the single line inside use().

Manage the stock of three products in a warehouse, with one instance per product. Product, with a ship method that lowers the stock, is already declared.

① Create instances for 12 monitor arms, 30 USB hubs, and 8 laptop stands, and put them in an array.

② Ship 5 units of the first product only.

③ Display "Name: N in stock" for every product, one per line.

④ Display the total stock in the form "Total: N".

JavaScript / TypeScript Editor

Run code to see output

Choosing by How Many You Need — Class vs. Object Literal

For data you use only once, such as a screen's display settings, writing { ... } on the spot is enough. But for data like coupons, where you create many items with the same shape and the same calculation, writing literals means copying even the body of the calculation into every item.

An object literal can have methods too: write apply(total) { ... } alongside the key-value pairs, and this inside it refers to that object. What differs from a class is where the method's body is written, and that difference shows up in the result of Object.keys.

// class: the body is written once, inside the definition
class Coupon {
  constructor(code, discount) {
    this.code = code;
    this.discount = discount;
  }
  apply(total) { return total - this.discount; }
}
const welcome = new Coupon("WELCOME", 500);

// Literal: the body is written out for every item
const summer = {
  code: "SUMMER",
  discount: 1000,
  apply(total) { return total - this.discount; },
};

// They have different keys
console.log(Object.keys(welcome).join(", "));   // code, discount
console.log(Object.keys(summer).join(", "));    // code, discount, apply

The body lives in a different place, but you call welcome.apply(3000) exactly like summer.apply(3000), so the calling code is the same. How an instance can call a method whose body lives in the class is covered in the article on prototypes. The table below compares the two approaches.

AspectObject literalclass
Objects createdJust the { } you wroteOne per new
Method bodyCopied into every itemWritten once in the class
Keys in Object.keyscode, discount, applycode, discount
instanceof Couponfalsetrue
Best forOne-off config or responsesMany items, same logic

No Commas After Methods in a Class

When you turn a literal into a class, leaving the comma after a method, as in apply(total) { ... },, gives you SyntaxError: Unexpected token ',', and none of the code runs. Inside a class's braces, methods simply follow one another with no separator.

QUIZ

Knowledge Check

Answer each question one by one.

Q1After class Coupon {}, what happens if you call Coupon("SPRING") without new?

Q2A variable discount set to 300 exists outside the class. What does welcome.apply(3000) return if this. is missing?

Q3What does Object.keys give for an instance of a class with constructor(code, discount) and apply?