Table of contents
- Understanding the Foundations: prototype and __proto__
- Functions as Objects: The Dual Nature of JavaScript Functions
- Creating Objects: Object.create and Manual Prototypes
- Automating Object Creation: The new Keyword
- Classes: Syntactic Sugar Over Prototypes
- Inheritance: Extending Prototypes
- Execution Context Diagrams
- Conclusion
JavaScript, the lingua franca of the web, has a unique and powerful object-oriented programming (OOP) paradigm rooted in its prototypal nature. Unlike traditional class-based OOP languages like Java or C++, JavaScript allows objects to inherit directly from other objects, making it dynamic and versatile. In this deep dive, we’ll explore everything from the fundamentals of prototype
and __proto__
to how constructs like Object.create
, the new
keyword, and modern ES6 class
syntax work under the hood.
If you’ve ever wondered how JavaScript achieves inheritance, or you’ve struggled to explain the difference between prototype
and __proto__
, this guide will clear things up. Let’s start from the basics and progressively get into the nitty-gritty details, complete with diagrams and code examples.
Understanding the Foundations: prototype
and __proto__
In JavaScript, every object has an internal link called [[Prototype]]
. This link connects the object to another object—its prototype. This prototype chain enables inheritance, allowing properties and methods to be shared across objects.
What is prototype
?
The prototype
property is a special property of functions. Every function in JavaScript is also an object, and as such, it has a prototype
property. This property is an object itself and acts as a blueprint for objects created using that function as a constructor.
Functions have a
prototype
.Objects do not have a
prototype
property, but they have__proto__
.
function User(name) {
this.name = name;
}
console.log(User.prototype); // {} (an empty object by default)
Functions as Objects: The Dual Nature of JavaScript Functions
In JavaScript, functions are both callable and objects. As objects, they have properties like prototype
.
function multiplyBy2(num) {
return num * 2;
}
multiplyBy2.stored = 5;
console.log(multiplyBy2.stored); // 5
console.log(typeof multiplyBy2.prototype); // object
This duality allows functions to serve as both constructors and blueprints for objects.
What is __proto__
?
The __proto__
property is present in all objects. It is a reference to the object’s prototype, which links it to the next object in the prototype chain.
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
The Relationship Between prototype
and __proto__
When a function is used as a constructor (via the new
keyword), the prototype
property of the constructor becomes the __proto__
of the created object. This is the core mechanism for JavaScript’s inheritance.
function User(name) {
this.name = name;
}
User.prototype.sayHello = function () {
console.log(`Hello, ${this.name}!`);
};
const user1 = new User('Alice');
// Prototype Chain
console.log(user1.__proto__ === User.prototype); // true
console.log(User.prototype.__proto__ === Object.prototype); // true
Diagram:
user1 -> __proto__ -> User.prototype -> __proto__ -> Object.prototype -> null
Creating Objects: Object.create
and Manual Prototypes
The Object.create
method provides a direct way to create objects and link them to a specific prototype. Unlike the new
keyword, Object.create
allows explicit control over the prototype chain.
Example: Using Object.create
const userPrototype = {
greet() {
console.log('Hello from the prototype!');
},
};
const user = Object.create(userPrototype);
user.name = 'Bob';
user.greet(); // Hello from the prototype!
console.log(user.__proto__ === userPrototype); // true
How Object.create
Works
Creates a new object.
Sets the new object’s
__proto__
to the specified prototype.Returns the new object.
Advantages:
Fine-grained control over prototypes.
Avoids unnecessary setup required by constructor functions.
Automating Object Creation: The new
Keyword
The new
keyword streamlines object creation by automating some of the manual steps involved with Object.create
. Here’s what happens when you use new
:
What Happens Behind the Scenes?
function User(name) {
this.name = name;
}
const user1 = new User('Charlie');
Create a new object.
- A new object is created.
Link the new object’s
__proto__
to the constructor’sprototype
.Set
this
inside the constructor to the new object.Execute the constructor function.
Return the new object.
console.log(user1.__proto__ === User.prototype); // true
Adding Methods to the Prototype
By attaching methods to the prototype
, all instances of a constructor function can share them.
User.prototype.sayHello = function () {
console.log(`Hello, ${this.name}!`);
};
user1.sayHello(); // Hello, Charlie!
Classes: Syntactic Sugar Over Prototypes
Introduced in ES6, classes provide a cleaner syntax for creating objects and managing inheritance. However, under the hood, they still use prototypes.
class User {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
const user1 = new User('Diana');
user1.sayHello(); // Hello, Diana!
What Happens Behind the Scenes?
A function
User
is created.The
User
function gets aprototype
property.Methods inside the class are added to
User.prototype
.
console.log(User.prototype.constructor === User); // true
Inheritance: Extending Prototypes
JavaScript allows inheritance via prototypes. The extends
keyword simplifies subclassing.
Example: Subclassing with extends
class Admin extends User {
constructor(name, accessLevel) {
super(name);
this.accessLevel = accessLevel;
}
showAccessLevel() {
console.log(`${this.name} has ${this.accessLevel} access.`);
}
}
const admin = new Admin('Eve', 'high');
admin.sayHello(); // Hello, Eve!
admin.showAccessLevel(); // Eve has high access.
Prototype Chain with extends
admin -> __proto__ -> Admin.prototype -> __proto__ -> User.prototype -> __proto__ -> Object.prototype -> null
How super
Works
The super
keyword calls the parent class’s constructor, ensuring this
is correctly initialized.
Execution Context Diagrams
To truly understand how JavaScript works, let’s visualize the execution context for some key operations.
Example: new
Keyword
function User(name) {
this.name = name;
}
User.prototype.sayHello = function () {
console.log(`Hello, ${this.name}!`);
};
const user1 = new User('Alice');
user1.sayHello();
Global Context:
User
function is stored in memory.user1
is declared but uninitialized.
Function Context (User):
this
is bound to the new object.Properties (
name
) are assigned tothis
.
Prototype Lookup:
user1.sayHello()
triggers a lookup.sayHello
is found onUser.prototype
.
Conclusion
Understanding JavaScript’s prototypal nature is crucial for mastering OOP in the language. From the intricacies of __proto__
and prototype
to modern classes and inheritance, JavaScript provides powerful tools for object-oriented design. Armed with this knowledge, you’re ready to tackle complex problems.
Happy coding!