Before the class keyword (ES2015), JavaScript used constructor functions to create many similar objects. You will still see them in older code, and they show how classes work underneath.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
return `Hi, I am ${this.name}`;
};
const maya = new Person("Maya", 28);
const arjun = new Person("Arjun", 31);
console.log(maya.greet()); // → Hi, I am Maya
console.log(maya.greet === arjun.greet); // → true (one shared function)
console.log(maya instanceof Person); // → true
By convention, constructor names start with a capital letter, and you call them with new.
What new does
new Person("Maya", 28) does four steps:
- Creates a new empty object.
- Sets that object’s prototype to
Person.prototype. - Runs the function with
thispointing at the new object. - Returns the object (unless the function returns a different object).
function Person(name) { this.name = name; }
const p = new Person("Maya");
console.log(Object.getPrototypeOf(p) === Person.prototype); // → true
console.log(p.constructor === Person); // → true
Why methods go on the prototype
If you write this.greet = function () {...} inside the constructor, every instance gets its own copy of the function, which wastes memory. Putting it on Person.prototype shares one copy.
function A() { this.hello = function () {}; }
function B() {}
B.prototype.hello = function () {};
console.log(new A().hello === new A().hello); // → false (two functions)
console.log(new B().hello === new B().hello); // → true (shared)
Forgetting new
function Person(name) { this.name = name; }
try {
const p = Person("Maya"); // forgot new: `this` is not a new object
console.log(p); // → undefined
} catch (error) {
console.log(error.name);
}
Without new, in sloppy mode this becomes the global object, and you accidentally set window.name. In strict mode it throws. Classes prevent this: calling a class without new is always an error.
Inheritance the old way
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () { return this.name + " makes a sound"; };
function Dog(name) { Animal.call(this, name); } // run the parent constructor
Dog.prototype = Object.create(Animal.prototype); // link the prototypes
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () { return this.name + " barks"; };
console.log(new Dog("Rex").speak()); // → Rex barks
console.log(new Dog("Rex") instanceof Animal); // → true
That is several lines of fiddly set-up, and it is easy to get wrong. This is the main reason class ... extends was added.
Same thing as a class
class Person {
constructor(name) { this.name = name; }
greet() { return `Hi, I am ${this.name}`; }
}
console.log(typeof Person); // → function
console.log(typeof Person.prototype.greet); // → function
The next lesson compares the two: Classes and Prototypes.