Q1After class Coupon {}, what happens if you call Coupon("SPRING") without new?
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
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'.
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
this is the variable written to the left of apply. Even with the same argument, a different this gives a different result.- Arguments
code/discount— usable only inside these braces this.discount = discount— stores the value on the instance
this.discount— reads the value stored on the instancetotal - discount— the name isn't visible, soReferenceError
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..
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
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().
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.
| Aspect | Object literal | class |
|---|---|---|
| Objects created | Just the { } you wrote | One per new |
| Method body | Copied into every item | Written once in the class |
| Keys in Object.keys | code, discount, apply | code, discount |
| instanceof Coupon | false | true |
| Best for | One-off config or responses | Many 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.
Knowledge Check
Answer each question one by one.
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?