Q1Parcel has static count = 0;. What do you get when you read first.count on an instance first?
Instance Members and static — Values and Methods That Belong to the Class
Learn how static puts values and methods on the class itself: counters with static fields, factory methods that build instances from JSON, shared constants, and when to use instance members instead.
To number parcels 1, 2, 3 in the order you create them, you need to keep track of how many you've created so far. The recipient differs from parcel to parcel, but that running count and the weight limit only need to exist once, shared by every parcel.
This article covers static, which puts values and methods on the class itself instead of on its instances.
Counting Parcels — static Fields
Suppose you want to number parcels in the order they're created. If you write the class field count = 0;, increase it with this.count += 1 in the constructor, and use it as the number, every parcel gets the number 1, because the field starts over from 0 on every new.
A static field (a property declared inside the class with the static keyword; only one copy exists, on the class itself) is read and written through the class name, as in Parcel.count.
In contrast, properties that each instance holds, like this.number, and methods written without static are called instance members.
class Parcel {
// The number of parcels created so far, stored once on the class
static count = 0;
constructor(recipient) {
Parcel.count += 1; // Increase the class's count by one
this.number = Parcel.count; // Use the count at this moment as the parcel number
this.recipient = recipient;
}
}
// Create two parcels
const first = new Parcel("Alice");
const second = new Parcel("Bob");
console.log(first.number, second.number); // 1 2
console.log(Parcel.count); // 2
console.log(first.count); // undefined
static count— 1 after the firstnew, 2 after the second- Read and write it as
Parcel.count
number— 1 (the count at creation)recipient— "Alice"- No
count—first.countisundefined
number— 2 (the count at creation)recipient— "Bob"
count, in the class's box, and none in the instances' boxes. Reading it from an instance gives undefined.Each call to new increases the same count in the class's box by one and copies its current value into the new instance's own number. Even after count goes up to 2, first.number stays at 1, the value it got when it was created.
Creating Parcels from JSON — static Methods
When you load a saved parcel, you get a JSON string containing the recipient and weight. If every screen that loads parcels has its own JSON.parse and new Parcel(...), a change to the JSON key names means fixing every one of those screens.
But when you create a parcel from JSON, there's no instance yet to call a method on. A static method (a method defined with static that you call through the class name without creating an instance) can be called as Parcel.fromJSON(text). A static method like fromJSON that creates and returns an instance is called a factory method.
class Parcel {
constructor(recipient, weightKg) {
this.recipient = recipient;
this.weightKg = weightKg;
}
label() {
return `${this.recipient} (${this.weightKg} kg)`;
}
// Take a JSON string, then create and return a Parcel instance
static fromJSON(text) {
const data = JSON.parse(text);
return new Parcel(data.recipient, data.weightKg);
}
}
const text = '{"recipient":"Alice","weightKg":2}';
const parcel = Parcel.fromJSON(text); // Called through the class name
console.log(parcel.label()); // Alice (2 kg)
JSON.parse gives you a plain object with properties but no methods. Only after going through new does it become an instance that can call label().The last line of fromJSON is new Parcel(...), so the constructor body runs as usual. If you put throw checks in the constructor, parcels loaded back from JSON go through the same checks as parcels entered on screen.
You Can't Call fromJSON on an Instance
Calling it on an instance, as in parcel.fromJSON(text), throws TypeError: parcel.fromJSON is not a function. Unlike label(), a static method can only be called through the class name. How this works is covered in the article on prototypes.
A Fixed Limit for Every Parcel — static Constants
The 30 kg weight limit is used both in the input form's notice, shown before any parcel exists, and in calculating each parcel's remaining capacity. If you put the limit in an instance field, you'd have to create a parcel with no recipient and no weight just to show the notice on the form.
A static constant (a static field holding a value that never changes while the program runs) is named in all caps, with words separated by underscores, as in static MAX_WEIGHT_KG = 30;. Whether you're outside the class or inside a method, you read it through the class name: Parcel.MAX_WEIGHT_KG.
class Parcel {
// The limit shared by all parcels is stored once on the class with static
static MAX_WEIGHT_KG = 30;
constructor(weightKg) {
this.weightKg = weightKg;
}
// How many more kg fit before the limit
remainingKg() {
return Parcel.MAX_WEIGHT_KG - this.weightKg;
// Writing this.MAX_WEIGHT_KG - this.weightKg returns NaN
}
}
// Readable even before any instance exists
console.log(`Up to ${Parcel.MAX_WEIGHT_KG} kg per parcel`); // Up to 30 kg per parcel
const parcel = new Parcel(12);
console.log(parcel.remainingKg()); // 18
The right column ends up with NaN because this inside a method refers to the instance, and static constants don't live on the instance. undefined - 12 doesn't throw an error, so you only notice when the displayed remaining weight looks wrong.
An All-Caps Name Can Still Be Reassigned
An all-caps name is only a convention that means "don't change this"; if you assign Parcel.MAX_WEIGHT_KG = 50;, the value changes. If you define only a getter, as in static get MAX_WEIGHT_KG() { return 30; }, assigning to it throws a TypeError (getters are covered in the next article).
Deciding Where a Value Lives — static vs. Instance
Once you know about static, it may seem convenient to put a value like the recipient on the class so you can read it from one place, Parcel.recipient. In a quick test that creates only one parcel, the same recipient is displayed whether it's static or on this, so you won't notice the difference.
Decide where a value lives by whether it differs for each item. A value put on this exists separately in each instance created by new, while a static value exists only once on the class, however many parcels you create.
// Recipient kept on static
class SharedParcel {
static recipient = "";
constructor(recipient) { SharedParcel.recipient = recipient; }
label() { return `To: ${SharedParcel.recipient}`; }
}
// Recipient kept on the instance
class Parcel {
constructor(recipient) { this.recipient = recipient; }
label() { return `To: ${this.recipient}`; }
}
const sharedFirst = new SharedParcel("Alice");
new SharedParcel("Bob");
console.log(sharedFirst.label()); // To: Bob
const first = new Parcel("Alice");
new Parcel("Bob");
console.log(first.label()); // To: Alice
sharedFirst has no recipient of its own, and label() reads SharedParcel.recipient as it is at the time of the call, so it shows "Bob", the last value assigned. The table below summarizes the parcel members from this article: how to write each kind and how to use it.
| What it holds | How to write and use it | Examples here |
|---|---|---|
| A value per item | Put on this, read from the instance | number / recipient / weightKg |
| Method reading per-item values | No static, call on the instance | label() / remainingKg() |
| One value for all items | With static, read via the class name | count / MAX_WEIGHT_KG |
| Logic needing no instance | With static, call via the class name | fromJSON(text) |
Knowledge Check
Answer each question one by one.
Q2What happens if you call a static method on the instance parcel, as in parcel.fromJSON(text)?
Q3A class keeps the recipient in a static field. If you create a parcel for "Alice" and then one for "Bob", what recipient does the first parcel's label() show?