Skillversitas

26. Prototypes

A prototype is JavaScript's fundamental mechanism for sharing properties and methods across objects. Every object in JavaScript has a link to its prototype, forming what is called the prototype chain.

Why Are Prototypes Important?

Classes in JavaScript are merely syntactic sugar over prototypes. Understanding prototypes helps you understand how JavaScript works under the hood, debug more effectively, and save memory.

// Every object has a [[Prototype]]
const student = { name: "Mike" };
console.log(Object.getPrototypeOf(student)); // Object.prototype

Code Example

Prototype Chain

const major = {
  name: "Computer Science",
  info() {
    return `Major ${this.name}`;
  }
};

const myClass = Object.create(major);
myClass.name = "CS-A";
myClass.capacity = 30;

console.log(myClass.info());         // "Major CS-A" (from the prototype)
console.log(myClass.info === major.info); // true (the same method!)

// Prototype chain:
// myClass → major → Object.prototype → null
console.log(Object.getPrototypeOf(myClass) === major); // true
console.log(Object.getPrototypeOf(major) === Object.prototype); // true

Object.create() — Creating an Object with a Specific Prototype

// Prototype for all students
const methodStudent = {
  getInfo() {
    return `${this.name} (${this.studentId}) - GPA: ${this.gpa}`;
  },

  hasPassed() {
    return this.gpa >= 2.0;
  },

  getClassification() {
    if (this.gpa >= 3.5) return "Cum Laude";
    if (this.gpa >= 3.0) return "Very Satisfactory";
    if (this.gpa >= 2.5) return "Memuaskan";
    return "Cukup";
  }
};

// Create a new student without a class — memory efficient!
function createStudent(name, studentId, gpa) {
  const student = Object.create(methodStudent);
  student.name = name;
  student.studentId = studentId;
  student.gpa = gpa;
  return student;
}

const mike = createStudent("Mike", "2024001", 3.75);
const anna = createStudent("Anna", "2024002", 3.20);
const john = createStudent("John", "2024003", 2.50);

console.log(mike.getInfo());    // method from the prototype
console.log(anna.getClassification()); // "Very Satisfactory"
console.log(john.hasPassed()); // true

// Check: methods are not duplicated
console.log(mike.getInfo === anna.getInfo); // true — memory efficient!

The prototype Property on Constructor Functions

function Student(name, studentId, major) {
  this.name = name;
  this.studentId = studentId;
  this.major = major;
  this.grades = [];
}

// Methods are added to the prototype — not duplicated per instance
Student.prototype.getInfo = function() {
  return `${this.name} | ${this.studentId} | ${this.major}`;
};

Student.prototype.addGrade = function(grade) {
  this.grades.push(grade);
};

Student.prototype.averageGrade = function() {
  if (this.grades.length === 0) return 0;
  const total = this.grades.reduce((a, b) => a + b, 0);
  return (total / this.grades.length).toFixed(2);
};

Student.prototype.isCumLaude = function() {
  return parseFloat(this.averageGrade()) >= 3.5;
};

const mike = new Student("Mike", "2024001", "CS");
mike.addGrade(3.8);
mike.addGrade(3.6);

console.log(mike.getInfo());         // from the prototype
console.log(mike.averageGrade());   // "3.70"
console.log(mike.isCumLaude());      // true

// All instances share the same methods
const anna = new Student("Anna", "2024002", "IS");
console.log(mike.addGrade === anna.addGrade); // true!

Modifying Built-in Prototypes (Careful!)

// Add a utility method to the Array prototype
Array.prototype.removeDuplicates = function() {
  return [...new Set(this)];
};

Array.prototype.shuffle = function() {
  const arr = [...this];
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
};

const myClass = ["Mike", "Anna", "John", "Mike", "Amy"];
console.log(myClass.removeDuplicates()); // ["Mike", "Anna", "John", "Amy"]
console.log(myClass.shuffle());          // shuffled

// String prototype
String.prototype.capitalizeWords = function() {
  return this.split(" ")
    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join(" ");
};

console.log("mike johnson".capitalizeWords()); // "Mike Johnson"
console.log("RI WA LAN DARI".capitalizeWords()); // "Ri Wa Lan Dari"

Prototypal vs Classical Inheritance

// Classical Inheritance (using a class)
class Animal {
  constructor(name) {
    this.name = name;
  }
  suara() {
    return "...";
  }
}

class Kucing extends Animal {
  suara() {
    return "Meong!";
  }
}

// Prototypal Inheritance (directly on objects)
const animal = {
  init(name) {
    this.name = name;
    return this; // fluent API
  },
  suara() {
    return "...";
  }
};

const kucing = Object.create(animal);
kucing.suara = function() {
  return "Meong!";
};

const kitty = Object.create(kucing).init("Kitty");
console.log(kitty.suara()); // "Meong!"
console.log(kitty.name);    // "Kitty"

Prototypes and hasOwnProperty

const university = {
  name: "Technology University",
  founded: 1990
};

const major = Object.create(university);
major.name = "Computer Science";
major.accreditation = "A";

// hasOwnProperty — check own properties (not from the prototype)
console.log(major.hasOwnProperty("name"));         // true
console.log(major.hasOwnProperty("founded"));      // false (from the prototype)
console.log("founded" in major);                   // true (includes the prototype)

// Check all properties (including the prototype)
for (const key in major) {
  if (major.hasOwnProperty(key)) {
    console.log(`${key}: ${major[key]}`); // only own properties
  }
}

Code Explanation

Concept Explanation
[[Prototype]] Internal link to the parent prototype (not directly accessible)
__proto__ Property accessor to [[Prototype]] (deprecated, use Object.getPrototypeOf)
Object.create(proto) Create a new object with a specific prototype
prototype property Only exists on functions — becomes the prototype for instances
hasOwnProperty() Checks whether a property is an own property, not from the prototype
instanceof Checks the prototype chain to determine a type

Prototype Chain

mike (instance)
  ↓ __proto__
Student.prototype (the constructor function's prototype)
  ↓ __proto__
Object.prototype
  ↓ __proto__
null (end of the chain)

Real-World Examples

An e-learning system with shared methods for memory efficiency:

// Shared prototype for all content
const CampusContent = {
  init(title, author) {
    this.title = title;
    this.author = author;
    this.date = new Date();
    this.views = 0;
    return this;
  },

  addView() {
    this.views++;
    return this; // chainable
  },

  getSummary() {
    return `"${this.title}" by ${this.author} - ${this.views} views`;
  }
};

// Different content types, different prototypes
const Material = Object.create(CampusContent);
Material.type = "Course Material";
Material.extraInfo = function() {
  return `${this.title} [${this.type}] - ${this.page || 0} page`;
};

const Assignment = Object.create(CampusContent);
Assignment.type = "Assignment";
Assignment.extraInfo = function() {
  const deadline = this.deadline
    ? new Date(this.deadline).toLocaleDateString("en-US")
    : "Not set";
  return `${this.title} [${this.type}] - Deadline: ${deadline}`;
};

// Factory functions
function createMaterial(title, author) {
  return Object.create(Material).init(title, author);
}

function createTask(title, author, deadline) {
  const assignment = Object.create(Assignment).init(title, author);
  assignment.deadline = deadline;
  return assignment;
}

// Usage
const sortingSlides = createMaterial("Algorithms Sorting", "Dr. John");
sortingSlides.page = 45;
sortingSlides.addView().addView().addView();

const taskASD = createTask("Implementation QuickSort", "Dr. John", "2026-08-01");
taskASD.addView().addView();

console.log(sortingSlides.extraInfo());
// "Algorithms Sorting [Course Material] - 45 page"

console.log(taskASD.extraInfo());
// "Implementation QuickSort [Assignment] - Deadline: 1/8/2026"

// All objects share methods from the prototype — memory efficient!
console.log(sortingSlides.addView === taskASD.addView); // true

Example: Prototype Chain for Campus Roles

const UserPrototype = {
  getInfo() {
    return `${this.name} (${this.email})`;
  }
};

const StudentPrototype = Object.create(UserPrototype);
StudentPrototype.enrollInCourse = function(course) {
  this.course = this.course || [];
  this.course.push(course);
};

const StudentRegulerPrototype = Object.create(StudentPrototype);
StudentRegulerPrototype.getInfo = function() {
  return `${super.getInfo()} [Reguler]`;
};

// Create instances
const user = Object.create(UserPrototype);
user.name = "Test";
user.email = "[email protected]";

const student = Object.create(StudentPrototype);
student.name = "Mike";
student.email = "[email protected]";
student.studentId = "2024001";

const reguler = Object.create(StudentRegulerPrototype);
reguler.name = "Anna";
reguler.email = "[email protected]";

console.log(student.getInfo());          // from UserPrototype
student.enrollInCourse("Algorithms");   // from StudentPrototype
console.log(student.course);         // ["Algorithms"]

Conclusion

  • Prototypes are how JavaScript shares methods across objects — more memory-efficient than duplicating methods in every instance.
  • Object.create(proto) creates an object with a specific prototype — ideal for prototypal inheritance.
  • The prototype chain runs from instance → constructor.prototype → Object.prototype → null.
  • hasOwnProperty() distinguishes own properties from inherited prototype properties.
  • Classes in JavaScript are syntactic sugar over prototypes — under the hood, prototypes are still used.