Sign in
Generates the clean JavaScript code you need.
Understand the foundation of JavaScript inheritance. This guide explains how prototype chains work, allowing objects to access shared properties and methods. Learn to write cleaner, more structured object-oriented code by grasping this core JavaScript mechanism.
The answer lies in the prototype chain if you have ever inspected a JavaScript object and wondered where built-in methods like .toString()
come from. This concept can be confusing, particularly for those with a background in class-based languages. This article offers a straightforward explanation of prototype chains and prototypal inheritance to help you write cleaner, more effective object-oriented code.
At its core, a JavaScript object is a collection of key-value pairs. These pairs can be data (properties) or functions (methods). Every object in JavaScript has an internal, hidden link to another object called its “prototype.” This prototype object also has its prototype, and so on, creating a linked sequence.
This structure allows objects to access properties and methods from other objects, which is the basis for inheritance in JavaScript. Understanding this mechanism is fundamental to grasping object-oriented programming within the language.
The prototype chain is the series of links between objects. When you try to access a property on an object, the JavaScript engine performs a specific sequence of steps:
This lookup process dictates how properties and methods are shared and accessed between objects. The prototype chain enables objects to share common properties, facilitating code reuse and inheritance of shared code in JavaScript.
The primary function of the prototype chain is to enable inheritance. This system supports:
A good grasp of prototype chains is beneficial for writing structured and maintainable applications.
" With the prototype chain, we can mimic inheritance. The prototype is a reference to another object, and it is used whenever JS can’t find the property you’re looking for on the current object."- Twitter
How an object is created determines its prototype chain. How an object's prototype is set during creation determines how it inherits properties and methods. Below are common methods for object creation in JavaScript.
A constructor function, also known as a constructor, is a blueprint for creating objects. The function’s prototype property becomes the prototype for all objects created using it, automatically setting up the prototype chain. You can add new properties to the constructor's prototype to extend the functionality of all instances.
1function Person(name) { 2 this.name = name; 3} 4 5Person.prototype.greet = function() { 6 console.log('Hello, ' + this.name); 7}; 8 9// Adding new properties to the prototype 10Person.prototype.age = 0; 11 12const person1 = new Person('Alice'); 13person1.greet(); // Outputs: Hello, Alice 14
Person.prototype
are shared among all Person objects because they exist on the constructor's prototype.This method creates a new object and lets you specify which object should be its prototype.
1const parent = { 2 greet() { 3 console.log('Hello from parent'); 4 } 5}; 6 7const child = Object.create(parent); 8child.name = 'Bob'; 9child.greet(); // Outputs: Hello from parent 10
Here, the child inherits directly from the parent. This is a direct way to set up inheritance and extend existing objects by setting their prototype. You can also create an object with a null prototype by passing null: Object.create(null)
. This results in an object that does not inherit from Object.prototype
, which is useful for creating pure dictionary-like objects.
The class syntax, introduced in ES6, offers a clearer, more modern way to create objects and manage inheritance. It is a cleaner syntax that operates on JavaScript’s existing prototypal inheritance system, allowing objects to inherit properties and methods through the prototype chain.
1class Person { 2 constructor(name) { 3 this.name = name; 4 } 5 greet() { 6 console.log('Hi ' + this.name); 7 } 8} 9
This code achieves a similar outcome to the constructor function example, but with more readable syntax.
The following diagram illustrates the lookup path, the JavaScript prototype chain, for an instance created from a Person constructor.
Each arrow points to the object’s prototype, showing the path JavaScript follows along the JavaScript prototype chain to find inherited properties.
When a property is accessed, the engine always checks for its own properties before looking at the prototype.
If you add a property to an instance with the same name as a property on its prototype, the instance's property will be used. This is called "property shadowing."
1const obj = { 2 greet() { 3 return 'Hello'; 4 } 5}; 6 7const newObj = Object.create(obj); 8newObj.greet = () => 'Hi'; 9 10console.log(newObj.greet()); // Outputs: Hi 11
This allows instances to override shared behavior. Be mindful that long prototype chains or extensive shadowing can affect performance because of the longer lookup times.
Choosing between a constructor function and Object.create
depends on your specific goal for inheritance and object setup. Here is a breakdown of how these two approaches compare.
Feature | Constructor Function | Object.create Method |
---|---|---|
Uses new keyword | Yes | No |
Prototype Set By | The constructor's .prototype property | The object passed as an argument |
Initialization Logic | Handled within the constructor | Must be added manually after creation |
Inheritance Style | Standard object-oriented setup | Direct and flexible |
To effectively use inheritance in JavaScript, you must know how to check and manage an object's prototype chain. These tools give you control over how objects inherit from one another.
You can examine an object's prototype to understand its inheritance structure.
Object.getPrototypeOf(): This is the standard and recommended method for retrieving an object's prototype.
JavaScript
1// Assuming a Person constructor exists from earlier examples 2function Person(name) { 3 this.name = name; 4} 5const person1 = new Person('Alice'); 6 7// Get the prototype of person1 8const personPrototype = Object.getPrototypeOf(person1); 9 10// Check if it's the Person.prototype object 11console.log(personPrototype === Person.prototype); // Outputs: true 12
proto: You may see the proto property used in older code or for debugging in a browser console. It is a legacy feature not part of the official language standard, so its use in production code is discouraged.
It is possible to change an object's prototype after it has been created, though this should be done carefully.
Object.setPrototypeOf(): This method sets the prototype of a specified object to another object or null.
1const animal = { 2 speak() { 3 console.log('Makes a sound'); 4 } 5}; 6 7const dog = { name: 'Rex' }; 8 9// Set the prototype of 'dog' to 'animal' 10Object.setPrototypeOf(dog, animal); 11 12dog.speak(); // Outputs: Makes a sound 13
Changing an object's prototype at runtime is slow and should be avoided in performance-sensitive code.
The structure of your prototype chains can affect application speed.
Object.setPrototypeOf()
can be particularly slow. When an object's prototype is changed, JavaScript engines often have to discard previous code optimizations and recompile, which can cause performance stalls. It is better to establish an object's prototype when it is created.Follow these practices to write clean and efficient code that uses prototypal inheritance.
Object.create()
, new, or class, rather than modifying it later with Object.setPrototypeOf()
.Object.getPrototypeOf()
for inspecting an object's prototype. Avoid the legacy proto property.Some behaviors related to prototypes can be confusing. Here is how to handle them.
obj.hasOwnProperty('propertyName')
. To debug, log both the object itself and its prototype (Object.getPrototypeOf(obj))
to see where the conflicting property is defined.Object.getPrototypeOf()
to inspect the chain link by link. Confirm that the prototype is the object you expect it to be.As JavaScript evolves, the prototype chain remains the core engine for its inheritance model. Modern syntax, most notably the class keyword, does not replace this underlying system. Instead, features like class provide a clearer and more familiar structure that operates on top of JavaScript's fundamental prototypal inheritance.
Future language additions should continue building upon these foundational concepts rather than replacing them. A solid understanding of the prototype chain is not just for working with older code; it provides deep insight into how objects truly behave. This knowledge is invaluable for debugging complex issues and comprehending how new language features operate.