An object literal makes one object. When you need many objects with the same shape (thirty students, a hundred products, a deck of cards) you need a way to stamp them out. That is what constructors are for.
const s1 = { name: "Ava", grade: 10, intro() { return `I'm ${this.name}`; } };
const s2 = { name: "Ben", grade: 11, intro() { return `I'm ${this.name}`; } };
const s3 = { name: "Cal", grade: 10, intro() { return `I'm ${this.name}`; } };
// ...copy-paste for every student, and hope you never mistype a key
The simplest fix is a regular function that builds and returns an object:
function makeStudent(name, grade) {
return {
name,
grade,
intro() { return `I'm ${this.name}`; }
};
}
const s1 = makeStudent("Ava", 10);
const s2 = makeStudent("Ben", 11);
console.log(s2.intro());
JavaScript has a built-in pattern for this: a function called with new. Inside it, this is the new object being built:
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
const s1 = new Student("Ava", 10);
const s2 = new Student("Ben", 11);
console.log(s1.name, s2.grade);
Student, not student). It signals "call me with new."
this inside the function to that objectreturn needed)const oops = Student("Dee", 9); // no new
console.log(oops); // undefined
new, no object is created and nothing is returned, so oops is undefined. Worse, this inside the call is the global window object, so this.name = name quietly sets window.name to "Dee". In strict mode it throws TypeError: Cannot set properties of undefined instead. The class syntax below prevents this mistake entirely.
A constructor can set properties that do not come from arguments. Every object it creates starts with that value:
function Person(first, last) {
this.firstName = first;
this.lastName = last;
this.nationality = "English"; // the same for everyone
}
const myFather = new Person("John", "Doe");
console.log(myFather.nationality);
Adding a property or method to an object that already exists affects only that one object:
const myMother = new Person("Sally", "Rally");
myFather.eyeColor = "blue"; // only myFather gets it
myMother.changeName = function (name) {
this.lastName = name; // only myMother gets it
};
console.log(myMother.eyeColor);
You might expect to add something to every Person by adding it to the constructor itself. That does not work:
Person.eyeColor = "brown";
console.log(myFather.eyeColor); // still blue: the constructor's own property is not shared
Person.changeName = function (name) { /* ... */ };
myFather.changeName("Smith");
Properties added to the constructor function belong to the function, not to the objects it makes. To give every object something new, add it to the constructor's prototype, which is next.
If you add a method inside the constructor with this.intro = function..., every object gets its own separate copy. Instead, put shared methods on the constructor's prototype: one copy that every object can use:
Student.prototype.intro = function() {
return `I'm ${this.name}, grade ${this.grade}`;
};
console.log(s1.intro());
console.log(s2.intro());
The class keyword is a cleaner way to write exactly the same thing. It is what you will see in most current code:
class Student {
constructor(name, grade) {
this.name = name;
this.grade = grade;
}
intro() { // automatically shared, like prototype
return `I'm ${this.name}, grade ${this.grade}`;
}
promote() {
this.grade++;
}
}
const ava = new Student("Ava", 10);
ava.promote();
console.log(ava.intro());
class will throw an error if you forget new, which is one of the reasons it is preferred. Classes can do much more (inheritance, private fields) than this course covers; you will see one extra piece, extends, in JS Error Object, where it is used to make custom error types.
console.log(ava instanceof Student); // true
console.log({} instanceof Student); // false
JavaScript ships with constructors for all of its native object types. Some you will use constantly:
const today = new Date(); // Dates section
const lookup = new Map(); // Maps section
const unique = new Set(); // Sets section
const err = new Error("Oops"); // Errors section
Others exist but have simpler literal forms that you should use instead:
| Instead of | Use |
|---|---|
| new Object() | {} |
| new Array() | [] |
| new RegExp("abc") | /abc/ |
| new Function("a", "return a") | a function expression or arrow |
Math is not on the list at all: it is a single global object, not a constructor, and new Math() is a TypeError: Math is not a constructor.
new String(), new Number(), or new Boolean(). They create wrapper objects that behave strangely: new Boolean(false) is truthy. Use plain literals ("text", 42, false) instead.
What does new do when calling new Student("Ava", 10)?
this to it, runs the function, and returns itnew. No return is needed in the constructor.What does this log?
function Dog(name) { this.name = name; }
Dog.prototype.speak = function() { return this.name + " says woof"; };
const d = new Dog("Rex");
console.log(d.speak());
Rex says woof. Methods on the prototype are shared by every object the constructor makes, and this is the object the method was called on.Why is class preferred over a constructor function?
newnew, and groups the constructor and methods in one clear blockclass is clearer and catches the forgotten-new mistake: TypeError: Class constructor Student cannot be invoked without 'new'.makeCar(make, year) and create three carsCar and create cars with newnew and see what you get backclass Car with an age() method that returns how old the car is