Function Prototype
Prototypes / Inheritance: The Prototype Property
What is the prototype property in JavaScript?
View Answer:
let animal = {
eats: true,
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal; // references animal
let rabbit = new Rabbit('White Rabbit'); // rabbit.__proto__ == animal
console.log(rabbit.eats); // true
Can you explain how the prototype property works in JavaScript?
View Answer:
function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }
let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}
console.log(rabbit.constructor == Rabbit); // true (from prototype)
What happens when you replace the default prototype in JavaScript?
View Answer:
function Rabbit() {}
Rabbit.prototype = {
jumps: true,
};
let rabbit = new Rabbit();
console.log(rabbit.constructor === Rabbit); // false
function Dog() {}
let dog = new Dog();
console.log(dog.constructor === Dog); // true
What is the difference between "proto" and prototype?
View Answer:
First let's clarify the concepts.
In JavaScript, each object has a __proto__
property which is an internal reference to the prototype object from which the instance object inherited. The prototype object is special type of enumerable object to which additional properties can be attached to it which will be shared across all the instances of its constructor function.
Here's an example that showcases the difference:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getFullName = function() {
return this.firstName + ' ' + this.lastName;
}
let john = new Person('John', 'Doe');
console.log(john.__proto__); // This will output the prototype object of the "john" instance
console.log(Person.prototype); // This will output the prototype object of the "Person" constructor function
console.log(john.__proto__ === Person.prototype); // This will output true, meaning both refer to the same object
console.log(john.getFullName()); // Output: John Doe. This is because "getFullName" method is defined in Person's prototype, so it's accessible to "john" instance.
In this example, you can see that john.__proto__
and Person.prototype
both refer to the same object. This is because john
was created with the Person
constructor, so its prototype (__proto__
) is the same as Person.prototype
.
The getFullName
method is defined on Person.prototype
, meaning it's not directly attached to john
object. However, it's still accessible to john
because john
's __proto__
points to Person.prototype
.
Note: The __proto__
property is considered deprecated and non-standard. It's better to use Object.getPrototypeOf(object)
method to get the prototype of an object.
How does prototypal inheritance work in JavaScript?
View Answer:
What is the purpose of the Object.prototype property?
View Answer:
Can you modify the prototype of an existing object?
View Answer:
let animal = {
speaks: true
};
let dog = {
bark: function() {
return 'Woof!';
}
};
// dog is an ordinary object, it doesn't have the 'speaks' property
console.log(dog.speaks); // undefined
// Set animal to be the prototype of dog
Object.setPrototypeOf(dog, animal);
// Now dog has 'speaks' property from its prototype chain
console.log(dog.speaks); // true
// dog can also access the 'bark' method that's directly on it.
console.log(dog.bark()); // 'Woof!'
In this code, dog
doesn't initially have a speaks
property. When animal
is set as the prototype of dog
, the dog
object can then access animal
's speaks
property.
Note: While it's possible to change the [[Prototype]] of an object, it's considered a bad practice in production code because it can lead to performance problems. In general, it's better to create the right prototype chain when creating objects. This method is there for completeness and should be used sparingly, if at all.
What happens if you look for a property or a method that's not present in the object but exists in the prototype chain?
View Answer:
let animal = {
speaks: true,
sound: function() {
return 'Generic animal sound!';
}
};
let dog = Object.create(animal); // animal is the prototype of dog
dog.bark = function() {
return 'Woof!';
};
console.log(dog.bark()); // 'Woof!', since 'bark' method exists directly on the 'dog' object
console.log(dog.speaks); // true, 'speaks' property doesn't exist directly on the 'dog', but exists in the prototype chain (in 'animal')
console.log(dog.sound()); // 'Generic animal sound!', 'sound' method doesn't exist directly on the 'dog', but exists in the prototype chain (in 'animal')
console.log(dog.meow); // undefined, 'meow' neither exists directly on the 'dog' nor in the prototype chain
In this example, when we call dog.speaks
or dog.sound()
, JavaScript first checks if these properties/methods exist directly on the dog
object. Since they don't, JavaScript then checks dog
's prototype, which is animal
. Since animal
has the speaks
property and sound
method, these values are returned.
However, when we try to access dog.meow
, JavaScript first checks the dog
object, and then its prototype animal
. Since neither has a meow
property, undefined
is returned.
What is a prototype chain?
View Answer:
What is the role of the constructor property?
View Answer:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
let john = new Person('John', 'Doe');
console.log(john.constructor); // [Function: Person]
In this code, john.constructor
is Person
, which is the function used to create john
.
The constructor
property is also useful when you want to create a new instance and you only have an instance of the object, but not the original constructor. Here's an example:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
let john = new Person('John', 'Doe');
let jane = new john.constructor('Jane', 'Doe');
console.log(jane.firstName); // Output: Jane
console.log(jane.lastName); // Output: Doe
Here, john.constructor
refers to the Person
function, which we can use to create a new Person
.
Note that the constructor
property can be overridden, so it's not a completely reliable way to determine the constructor of an object. The instanceof
operator is generally a better choice for that.
What's the difference between prototypal and classical inheritance?
View Answer:
Let's see an example of both:
Classical Inheritance (Simulation in JavaScript)
// Constructor for Superclass
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + ' makes a sound.';
}
// Constructor for Subclass
function Dog(name) {
Animal.call(this, name); // Call the parent's constructor
}
// Establish the prototype chain to inherit methods
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // Repair the constructor reference
Dog.prototype.bark = function() {
return this.name + ' barks.';
}
var dog = new Dog('Rex');
console.log(dog.speak()); // Output: Rex makes a sound.
console.log(dog.bark()); // Output: Rex barks.
In this example, we simulate classical inheritance using constructor functions. Dog
is a subclass of Animal
and inherits its methods.
Prototypal Inheritance
var animal = {
init: function(name) {
this.name = name;
},
speak: function() {
return this.name + ' makes a sound.';
}
};
var dog = Object.create(animal);
dog.bark = function() {
return this.name + ' barks.';
}
var rex = Object.create(dog);
rex.init('Rex');
console.log(rex.speak()); // Output: Rex makes a sound.
console.log(rex.bark()); // Output: Rex barks.
In this prototypal inheritance example, we directly create objects from other objects. rex
is an object created from dog
, which is created from animal
. The methods from animal
are available to dog
and rex
through the prototype chain.
In prototypal inheritance, an object can directly inherit from another object. This is different from classical inheritance where classes inherit from classes.
What are some common pitfalls with prototypes and inheritance in JavaScript?
View Answer:
What is the prototype property used for?
View Answer:
Do all objects in JavaScript have a prototype?
View Answer:
let obj = {};
console.log(obj.__proto__ === Object.prototype); // true
function Func() {}
console.log(Func.__proto__ === Function.prototype); // true
let arr = [];
console.log(arr.__proto__ === Array.prototype); // true
In this code, obj
is an object literal, so its prototype is Object.prototype
. Func
is a function, so its prototype is Function.prototype
. arr
is an array, so its prototype is Array.prototype
.
However, it's possible to create an object without a prototype using Object.create(null)
. Such objects do not inherit anything, including basic methods like toString()
:
let noProto = Object.create(null);
console.log(noProto.__proto__); // undefined
In this code, noProto
does not have a prototype, so noProto.__proto__
is undefined
. Attempting to call toString()
on noProto
would result in an error.
What is shadowing in JavaScript with regards to prototypes?
View Answer:
let animal = {
speak: function() {
return 'The animal makes a sound!';
}
};
let dog = Object.create(animal);
dog.speak = function() {
return 'The dog barks!';
};
console.log(dog.speak()); // Output: The dog barks!
In this code, dog
is created with animal
as its prototype. animal
has a speak
method, and dog
also has a speak
method. When dog.speak()
is called, JavaScript first looks for a speak
method on the dog
object. Since it finds one, it uses that method and does not continue looking up the prototype chain.
If the speak
method is deleted from dog
, then the speak
method from animal
is used instead:
delete dog.speak;
console.log(dog.speak()); // Output: The animal makes a sound!
In this code, after the speak
method is deleted from dog
, dog.speak()
outputs 'The animal makes a sound!'. This is because JavaScript doesn't find a speak
method on dog
, so it looks up the prototype chain and finds the speak
method on animal
.
Can you remove properties from a prototype?
View Answer:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getFullName = function() {
return this.firstName + ' ' + this.lastName;
}
let john = new Person('John', 'Doe');
console.log(john.getFullName()); // Output: John Doe
// Deleting property from prototype
delete Person.prototype.getFullName;
console.log(john.getFullName); // Output: undefined
In this example, john.getFullName()
initially outputs 'John Doe'. After getFullName
is deleted from Person.prototype
, john.getFullName
is undefined
.
While it's possible to delete properties from a prototype, it's generally not a good idea because it can have unexpected side effects. For example, if other code is depending on that property being present in the prototype, that code could stop working correctly.
Can we use arrow functions for prototype methods?
View Answer:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getFullName = () => {
return this.firstName + ' ' + this.lastName;
}
let john = new Person('John', 'Doe');
console.log(john.getFullName()); // Output: undefined undefined
In this case, the getFullName
method is an arrow function, so this
doesn't refer to the john
object. Instead, it refers to the surrounding scope, which in a non-strict global context is the window
object (or global
object in Node.js environment). Since window.firstName
and window.lastName
are not defined, john.getFullName()
returns "undefined undefined".
Compare this with a traditional function:
Person.prototype.getFullName = function() {
return this.firstName + ' ' + this.lastName;
}
console.log(john.getFullName()); // Output: John Doe
In this case, the getFullName
method is a traditional function, so this
refers to the john
object. john.getFullName()
therefore correctly returns "John Doe".
For this reason, arrow functions are generally not used as object methods when those methods need to access other properties of the object.
Why do we say JavaScript has a dynamic prototype?
View Answer:
How does the JavaScript engine find a property in a prototype chain?
View Answer:
What is the difference between a prototype and an instance in JavaScript?
View Answer:
// Define a constructor function
function Car(make, model) {
this.make = make;
this.model = model;
}
// Add a method to the prototype
Car.prototype.displayCar = function() {
return this.make + ' ' + this.model;
}
// Create a new instance of Car
let myCar = new Car('Toyota', 'Corolla');
console.log(myCar.displayCar()); // Outputs: Toyota Corolla
In this code:
Car
is a constructor function. It defines a blueprint for creating new car objects.Car.prototype.displayCar
is a method added to the prototype ofCar
. This method will be shared by all instances ofCar
.myCar
is an instance ofCar
. It's an object created from theCar
constructor, and it has access to properties and methods defined in theCar
constructor and theCar
prototype.
So the main difference is that an instance is an individual object created from a constructor, while a prototype is an object that serves as a blueprint for instances. Changes to the prototype affect all instances, while changes to an instance only affect that instance.
What is the default prototype of an object created using an object literal?
View Answer:
What is Object.create() and how does it relate to prototypes?
View Answer:
let animal = {
speaks: true,
sound: function() {
return 'Generic animal sound!';
}
};
let dog = Object.create(animal);
dog.bark = function() {
return 'Woof!';
};
console.log(dog.speaks); // true, inherited from 'animal' via prototype chain
console.log(dog.sound()); // 'Generic animal sound!', inherited from 'animal' via prototype chain
console.log(dog.bark()); // 'Woof!', present directly on 'dog'
In this example, animal
is used as a prototype for creating dog
with Object.create()
. As a result, dog
has access to the speaks
property and the sound
method via the prototype chain, while also having its own bark
method directly on it.