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.

Image description

All built-in objects ultimately inherit from Object prototype:

Image description

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 ...