Mastering JavaScript Prototypes: From Objects to Classes

JavaScript · Prototypes · Classes · Objects

Mastering JavaScript Prototypes: From Objects to Classes

Learn how JavaScript objects use prototypes to share methods, how constructor functions and Object.create() work, and why classes are just syntactic sugar for prototypes.

Oscar Mutwiri

Oscar Mutwiri

Software engineer

11/8/2025 · 4 min read

Prototypes are like a hidden blueprint that every object uses to find properties and methods it doesn't have itself. To understand better, let's look at the following code:

function studentCreator(name, age) {
    const newStudent = {};
    newStudent.name = name;
    newStudent.age = age;
    newStudent.introduce = function() {
        console.log(`Hi, my name is ${name} and I'm ${age} years old`)
    };
    return newStudent;
}

const student1 = studentCreator("Killy", 16);
const student2 = studentCreator("Missy", 17);
student1.introduce();
student2.introduce();

This code can be used to create student data and also reference their name and age using the introduce function for introductions. This code, however, has flaws; it might look correct, but each user object is going to have the introduce function, and we have to add new students manually. To prevent the function from appearing on all the student objects, we can create the function outside our studentCreator and create a link to it.

const studentFunctions = {
    introduce: function () {
        console.log(`Hi, my name is ${this.name} and I'm ${this.age} years old`);
    }
}

function studentCreator(name, age) {
    const newStudent = Object.create(studentFunctions);
    newStudent.name = name;
    newStudent.age = age;

    return newStudent;
}

const student1 = studentCreator("Killy", 16);
const student2 = studentCreator("Missy", 17);
student1.introduce();
student2.introduce();

When we use Object.create() to create a new user, it creates a link to studentFunctions, which can now be accessed by all the student objects. With this kind of setup, one question arises: since the student objects don't have the function as before, how is the link created, enabling it to be referenced? All objects have a __proto__ property by default, which links to an object with useful functions. In our case, student1 would be { name: "Killy", age: 16, __proto__: { } }. When we use Object.create(studentFunctions) in our studentCreator, we add the link to studentFunctions in every object's proto. This means our student1 would look like this: { name: "Killy", age: 16, proto -> studentFunctions }. When we call student1.introduce(), JS checks the student1 object, discovers the function is not there, but does not return an error; it proceeds to check proto, where it finds a link to the function. Note, proto is a non-standard access (student1.__proto__); using Object.getPrototypeOf() Object.getPrototypeOf(student1) is safer.

All this can be automated using the new keyword when creating a new object. We first need to understand that every function is a dual entity, meaning it has the callable function and an object. The object has a property called "prototype" that is assigned to an object containing a constructor property. This constructor references back to the function that built the object. { prototype: { constructor: FunctionName } }.

function studentCreator (name, age) {
    this.name = name;
    this.age = age;
}

studentCreator.prototype.introduce = function () { console.log(`Hi, my name is ${this.name} and I'm ${this.age} years old`); };

const student1 = new studentCreator("Killy", 16);
student1.introduce()

In the above code, the function studentCreator has two parts: the callable function and the object. Inside the object, we have the prototype assigned to an object with the constructor property. { prototype: { constructor: studentCreator } }. Then when the line studentCreator.prototype.introduce = { ... } is executed, it adds the function introduce to the prototype. \{ prototype: { constructor: studentCreator, introduce: (the introduce function) } }. We then create a new student using the new keyword. This is what we get as the value of student1: { name: "Killy", age: 16, __proto__: studentCreator.prototype}. This means when we call student1.introduce, we get the introduction statement since we have access to the studentCreator prototype, which contains the function introduce. Note that if we run console.log(student1.constructor === studentCreator), we would get true, because we saw that the constructor references back to the function that created the object.

To make this code cleaner or less error-prone, classes were introduced. Classes are just syntactic sugar built on top of the entire prototype-constructor system. When we write:

class StudentCreator {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    introduce() {
        console.log(`Hi, my name is ${this.name} and I'm ${this.age} years old`);
    }
}
const student1 = new StudentCreator("Killy", 16);
student1.introduce();

The constructor block is literally the function body that gets called when using the new keyword, just like the function studentCreator in our earlier code. The constructor constructs the object of the user with properties named "name," "age," and "proto," linking to the class's prototype. Then all the functions that come after that are all put in the class's prototype, allowing all objects created from the class to access them.