An object has its own properties. However, if it cannot find a specific property, it can look for that property in another object it references through its prototype. Objects have a hidden [[Prototype]] property, which points to another object. This is illustrated below:
Here, the rabbit
object inherits from animal
, so we can use the walk
method defined in animal
:
Examples are from The Modern JavaScript Tutorial
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
// walk is taken from the prototype
rabbit.walk(); // Animal walk
If we try to read a property of an object and it doesn't exist, JavaScript continues searching for it in the prototype chain. This behavior can be extended further:
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)
this
Here is another example.
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith
admin.fullName = "Alice Cooper";
alert(admin.fullName); // Alice Cooper, state of admin modified alert(user.fullName); // John Smith, state of user protected
Explanation: What does this
refer to?
Here, the keyword this
always refers to the object before the dot when a property or method is accessed.
In admin.fullName
, this
refers to admin
, so the fullName
getter and setter operate on admin's properties.
Even though fullName
is inherited from user
, the logic in the getter/setter applies to the object (admin
) that invoked it.
Using Object.create
Instead of __proto__
The use of __proto__
is now discouraged in modern JavaScript due to performance concerns and potential issues. The same functionality can be achieved using Object.create
:
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = Object.create(user);
// Output logged to the console
console.log(admin.fullName); // John Smith
admin.fullName = "Alice Cooper";
console.log(admin.fullName); // Alice Cooper
console.log(user.fullName); // John Smith
JavaScript has built-in objects, such as Array
, Function
, and Map
. These objects store their methods in their prototypes, such as slice()
for arrays and size()
for maps. When we create a new object using new Object()
or let obj = {}
, the [[Prototype]]
is automatically set to Object.prototype
.
All built-in objects ultimately inherit from Object
prototype:
You can see 5 being used on the right. Isn't it a primitive and not an object? That's correct --- 5 is a primitive, not an object. However, JavaScript has wrapper objects for primitives. When they try to access a method on a primitive, JavaScript temporarily wraps the primitive in an object, like Number
, String
, or Boolean
, to provide access to its methods. This wrapper disappears immediately after use. It's important to note that null
and undefined
do not have wrapper objects.
Objects in JavaScript not only store their own properties but also have prototypes. Prototypes allow objects to inherit properties and methods from other objects, which helps save memory by sharing functionality instead of duplicating it.
One important limitation is that objects cannot circularly reference each other or reference two objects simultaneously in the prototype chain.
For example, the following is not allowed:
B <-- A --> C.
Always follow a linear structure, like
A --> B --> C ...