178 lines
6.9 KiB
Plaintext
178 lines
6.9 KiB
Plaintext
---
|
||
title: Explain how prototypal inheritance works
|
||
---
|
||
|
||
## TL;DR
|
||
|
||
Prototypical inheritance in JavaScript is a way for objects to inherit properties and methods from other objects. Every JavaScript object has a special hidden property called `[[Prototype]]` (commonly accessed via `__proto__` or using `Object.getPrototypeOf()`) that is a reference to another object, which is called the object's "prototype". When a property is accessed on an object and if the property is not found on that object, the JavaScript engine looks at the object's `__proto__`, and the `__proto__`'s `__proto__` and so on, until it finds the property defined on one of the `__proto__`s or until it reaches the end of the prototype chain. This behavior simulates classical inheritance, but it is really more of [delegation than inheritance](https://davidwalsh.name/javascript-objects).
|
||
|
||
## Example of Prototypal Inheritance
|
||
|
||
```js
|
||
// Parent object constructor.
|
||
function Animal(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
// Add a method to the parent object's prototype.
|
||
Animal.prototype.makeSound = function () {
|
||
console.log('The ' + this.constructor.name + ' makes a sound.');
|
||
};
|
||
|
||
// Child object constructor.
|
||
function Dog(name) {
|
||
Animal.call(this, name); // Call the parent constructor.
|
||
}
|
||
|
||
// Set the child object's prototype to be the parent's prototype.
|
||
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
|
||
|
||
// Add a method to the child object's prototype.
|
||
Dog.prototype.bark = function () {
|
||
console.log('Woof!');
|
||
};
|
||
|
||
// Create a new instance of Dog.
|
||
const bolt = new Dog('Bolt');
|
||
|
||
// Call methods on the child object.
|
||
console.log(bolt.name); // "Bolt"
|
||
bolt.makeSound(); // "The Dog makes a sound."
|
||
bolt.bark(); // "Woof!"
|
||
```
|
||
|
||
Things to note are:
|
||
|
||
- `.makeSound` is not defined on `Dog`, so the engine goes up the prototype chain and finds `.makeSound` off the inherited `Animal`.
|
||
- Using `Object.create` to build the inheritance chain is no longer recommended. Use `Object.setPrototypeOf` instead.
|
||
|
||
---
|
||
|
||
## Prototypical Inheritance in Javascript
|
||
|
||
Prototypical inheritance is a feature in JavaScript used to create objects that inherit properties and methods from other objects. Instead of a class-based inheritance model, JavaScript uses a prototype-based model, where objects can directly inherit from other objects.
|
||
|
||
### Key Concepts
|
||
|
||
1. **Prototypes** : Every object in Javascript has a prototype, which is another object. When you create an object using an object literal or a constructor function, the new object is linked to the prototype of its constructor function or the `Object.prototype` if no prototype is specified. This is commonly referenced using `__proto__` or `[[Prototype]]`. You can also get the prototype by using inbuilt method `Object.getPrototypeOf()` and you can set the prototype of an object via `Object.setPrototypeOf()`.
|
||
|
||
```js
|
||
// Define a constructor function
|
||
function Person(name, age) {
|
||
this.name = name;
|
||
this.age = age;
|
||
}
|
||
|
||
// Add a method to the prototype
|
||
Person.prototype.sayHello = function () {
|
||
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
|
||
};
|
||
|
||
// Create a new object using the constructor function
|
||
let john = new Person('John', 30);
|
||
|
||
// The new object has access to the methods defined on the prototype
|
||
john.sayHello(); // "Hello, my name is John and I am 30 years old."
|
||
|
||
// The prototype of the new object is the prototype of the constructor function
|
||
console.log(john.__proto__ === Person.prototype); // true
|
||
|
||
// You can also get the prototype using Object.getPrototypeOf()
|
||
console.log(Object.getPrototypeOf(john) === Person.prototype); // true
|
||
|
||
// You can set the prototype of an object using Object.setPrototypeOf()
|
||
let newProto = {
|
||
sayGoodbye: function () {
|
||
console.log(`Goodbye, my name is ${this.name}`);
|
||
},
|
||
};
|
||
|
||
Object.setPrototypeOf(john, newProto);
|
||
|
||
// Now john has access to the methods defined on the new prototype
|
||
john.sayGoodbye(); // "Goodbye, my name is John"
|
||
|
||
// But no longer has access to the methods defined on the old prototype
|
||
console.log(john.sayHello); // undefined
|
||
```
|
||
|
||
2. **Prototype Chain**: When a property or method is accessed on an object, JavaScript first looks for it on the object itself. If it doesn't find it there, it looks at the object's prototype, and then the prototype's prototype, and so on, until it either finds the property or reaches the end of the chain (i.e., null).
|
||
|
||
3. **Constructor Functions**: JavaScript provides constructor functions to create objects. When a function is used as a constructor with the new keyword, the new object’s prototype ([[Prototype]]) is set to the constructor’s prototype property.
|
||
|
||
```js
|
||
// Define a constructor function
|
||
function Animal(name) {
|
||
this.name = name;
|
||
}
|
||
|
||
// Add a method to the prototype
|
||
Animal.prototype.sayName = function () {
|
||
console.log(`My name is ${this.name}`);
|
||
};
|
||
|
||
// Define a new constructor function
|
||
function Dog(name, breed) {
|
||
Animal.call(this, name);
|
||
this.breed = breed;
|
||
}
|
||
|
||
// Set the prototype of Dog to be a new instance of Animal
|
||
Dog.prototype = Object.create(Animal.prototype);
|
||
|
||
// Add a method to the Dog prototype
|
||
Dog.prototype.bark = function () {
|
||
console.log('Woof!');
|
||
};
|
||
|
||
// Create a new object using the Dog constructor function
|
||
let fido = new Dog('Fido', 'Labrador');
|
||
|
||
// The new object has access to the methods defined on its own prototype and the Animal prototype
|
||
fido.bark(); // "Woof!"
|
||
fido.sayName(); // "My name is Fido"
|
||
|
||
// If we try to access a method that doesn't exist on the Dog prototype or the Animal prototype, JavaScript will return undefined
|
||
console.log(fido.fly); // undefined
|
||
```
|
||
|
||
4. **Object.create()**: This method creates a new object with the specified prototype object and properties. It's a straightforward way to set up prototypical inheritance. If you create a object via `Object.create(null)` it will not inherit any properties from `Object.prototype`. This means the object will not have any built-in properties or methods like `toString()`, `hasOwnProperty()`,
|
||
|
||
```js
|
||
// Define a prototype object
|
||
let proto = {
|
||
greet: function () {
|
||
console.log(`Hello, my name is ${this.name}`);
|
||
},
|
||
};
|
||
|
||
// Use `Object.create()` to create a new object with the specified prototype
|
||
let person = Object.create(proto);
|
||
person.name = 'John';
|
||
|
||
// The new object has access to the methods defined on the prototype
|
||
person.greet(); // "Hello, my name is John"
|
||
|
||
// Check if the object has a property
|
||
console.log(person.hasOwnProperty('name')); // true
|
||
|
||
// Create an object that does not inherit from Object.prototype
|
||
let animal = Object.create(null);
|
||
animal.name = 'Rocky';
|
||
|
||
// The new object does not have any built-in properties or methods
|
||
console.log(animal.toString); // undefined
|
||
console.log(animal.hasOwnProperty); // undefined
|
||
|
||
// But you can still add and access custom properties
|
||
animal.describe = function () {
|
||
console.log(`Name of the animal is ${this.name}`);
|
||
};
|
||
|
||
animal.describe(); // "Name of the animal is Rocky"
|
||
```
|
||
|
||
## Resources
|
||
|
||
- [Inheritance and the prototype chain | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)
|