Q1In the constructor of a class that has status = "Open";, what happens when you read this.status?
constructor and Class Fields — Setting Initial Values
Learn how the constructor and class fields set an instance's initial values: storing arguments, the order in which fields are added, defaults for omitted arguments, and rejecting invalid values.
A support ticket has some values that differ from ticket to ticket, like its title, and some initial values that are the same for every ticket, like the status "Open". If you write the class carelessly, omitted or invalid values end up stored on the instance.
This article covers the constructor, which takes the values passed in at creation, and class fields, which declare fixed initial values.
Cleaning Up Arguments Before Storing Them — The constructor Body
Ticket titles submitted through the support form sometimes have extra spaces at the start or end. If you build a display string like "Alice: Can't log in" after the instance is created, every screen that shows it needs its own trim and string-building code.
In the constructor body, you don't have to store an argument unchanged. You can store a cleaned-up version of it, as in this.title = title.trim(), or a value built from several arguments. Every screen then just reads the instance's properties and gets values that are already clean.
class Ticket {
constructor(title, customerName) {
// Clean up the argument before storing it on this
this.title = title.trim();
this.customerName = customerName;
// Build the display value from this, after the cleanup
this.label = `${this.customerName}: ${this.title}`;
// Writing `${customerName}: ${title}` would keep the spaces in the title
}
}
// Pass a title with spaces before and after it
const ticket = new Ticket(" Can't log in ", "Alice");
console.log(ticket.title); // Can't log in
console.log(ticket.label); // Alice: Can't log in
The top row reads the argument title, and the bottom row reads this.title after trim. If you move the label line to the top of the constructor, neither this.title nor this.customerName has been assigned yet, so label becomes undefined: undefined.
Listing Fixed Initial Values — Class Fields
Every incoming ticket starts with the status "Open" and zero comments. If you set initial values like these, which have nothing to do with the arguments, in the constructor with this.status = "Open";, they get mixed in with the lines that store the arguments, and you have to read the whole body to see what an instance holds.
A class field (a property declared inside the class's braces but outside the constructor and methods, and added to the instance on every new) is just a name and an initial value, with no this, as in status = "Open";. To read it from the constructor body or a method, you write this.status, just like any other property.
class Ticket {
// Initial values that don't depend on the arguments go in fields
status = "Open";
comments = [];
constructor(title) {
this.title = title;
this.summary = `[${this.status}] ${title}`; // Field values can be read here
}
}
const first = new Ticket("Can't log in");
const second = new Ticket("Invoice not received");
console.log(first.summary); // [Open] Can't log in
// comments is a separate array created on each new
first.comments.push("Asked the customer to restart");
console.log(first.comments.length); // 1
console.log(second.comments.length); // 0
new, the fields are added first, and then the constructor body runs. The body can already read the field values.Step ① runs again on every new, so comments = [] becomes a separate array for each instance, and a comment added to first doesn't end up in second. That's why second.comments.length stays at 0.
Writing this. on a Field Is a SyntaxError
If you write this.status = "Open"; where fields go (outside the constructor), you get SyntaxError: Unexpected token '.', and none of the code runs. Adding let or const is a SyntaxError too. A field is just a name and an initial value.
Deciding the Value When an Argument Is Omitted — Default Parameters
Most tickets have the priority "Normal", and you want to pass "Urgent" only when something is pressing. If you write the field priority = "Normal"; and also add this.priority = priority; to the body, a ticket created without a priority doesn't end up with "Normal".
A default parameter (syntax that sets the value to use when an argument isn't passed), which you saw in the article on default and rest parameters, also works in a constructor: write priority = "Normal" in the parameter list. An omitted or undefined argument is replaced with the default before the body runs, so this.priority = priority receives "Normal".
// A default in the field, plus assigning the argument in the body
class DraftTicket {
priority = "Normal";
constructor(title, priority) {
this.title = title;
this.priority = priority; // The priority on the right is the argument (undefined if omitted)
}
}
console.log(new DraftTicket("Can't log in").priority); // undefined
// For a value an argument can change, put the default in a default parameter
class Ticket {
constructor(title, priority = "Normal") {
this.title = title;
this.priority = priority;
}
}
console.log(new Ticket("Can't log in").priority); // Normal
console.log(new Ticket("Invoice not received", "Urgent").priority); // Urgent
undefined. What's left at the end is the value assigned in the body.Put values that an argument can change in default parameters, and values that don't depend on the arguments in fields. For a value that depends on a condition on the arguments, like the shipping fee in Exercise 2, use an if in the body to change the field's initial value. The table below shows what ends up in priority with each approach, with and without "Urgent", including a version that never assigns it in the body.
| Approach | Title only | Also passing "Urgent" |
|---|---|---|
| Field only (no assignment in body) | Normal | Normal (value ignored) |
| Field + this.priority = priority | undefined | Urgent |
| Default parameter priority = "Normal" | Normal | Urgent |
Rejecting Invalid Values — throw in the constructor
If you end up with a ticket that has an empty title, or a priority that isn't one of the agreed values, like "High", the list shows a blank row, and the ticket is left out when you count the "Urgent" ones. Even if you write a separate function to check tickets after they're created, any screen that forgets to call it keeps using the invalid instance.
For validation at creation (checking, at the moment you create an instance, that the values you received meet the rules), write an if inside the constructor and throw an exception with throw new Error(...) when a value doesn't pass. Once an exception is thrown, the new expression doesn't return an instance.
class Ticket {
constructor(title, priority = "Normal") {
// Reject invalid values with an exception before they reach this
if (title.trim() === "") {
throw new Error("Title is empty");
}
if (priority !== "Normal" && priority !== "Urgent") {
throw new Error(`Priority must be Normal or Urgent: ${priority}`);
}
this.title = title.trim();
this.priority = priority;
}
}
try {
const ticket = new Ticket("Can't log in", "High");
console.log(ticket.title); // This line never runs
} catch (error) {
console.log(error.message); // Priority must be Normal or Urgent: High
}
const ticket = new Ticket(...)— the right side throws, so nothing goes intoticketconsole.log(ticket.title)— never runs
- Title check — not empty, so it passes
- Priority check — it's
"High", so it runsthrow this.title = ...— nothing from here down runs
error.message— "Priority must be Normal or Urgent: High"
new expression, skips the rest of try, and lands in catch. The invalid instance never ends up in any variable.With the check in the constructor, no ticket can skip it, no matter how many places call new Ticket(...). If you include the rejected value in the exception message, the catch side can also show which value was invalid.
Assignments After Creation Aren't Checked
The check in the constructor only runs during new. If you assign ticket.priority = "High"; after the ticket is created, no error occurs and the value is simply overwritten. A way to check the value on every assignment is covered in the article on getters and setters.
Knowledge Check
Answer each question one by one.
Q2A class has only the field priority = "Normal"; and doesn't assign it in the body. What happens if you pass "Urgent"?
Q3Inside try, the constructor throws during const ticket = new Ticket(""). What happens?