Every constructor function is equipped with a prototype property that points to its associated prototype object. This prototype object, in turn, contains a constructor property that references the constructor function itself, creating a bidirectional link.
/**
* Standard prototype chains eventually terminate at Object.prototype.
* The prototype of Object.prototype is null.
*/
console.log(User.prototype.__proto__ === Object.prototype); // true
console.log(User.prototype.__proto__.constructor === Object); // true
console.log(User.prototype.__proto__.__proto__ === null); // true
let userA = new User();
let userB = new User();
It is crucial to distinguish between the three distinct entities involved: the constructor, the prototype object, and the instance.
console.log(userA !== User.prototype); // true
console.log(User.prototype !== User); // true
/**
* Instances connect to the prototype via __proto__ (internal [[Prototype]]).
* Constructors connect to the prototype via the prototype property.
* Instances do not directly reference the constructor.
*/
console.log(userA.__proto__ === User.prototype); // true
console.log(userA.__proto__.constructor === User); // true
Instances generated by the same constructor share a single prototype object.
/**
* The instanceof operator checks if the prototype chain
* includes the specified constructor's prototype.
*/
console.log(userA instanceof User); // true
console.log(userA instanceof Object); // true
console.log(User.prototype instanceof Object); // true
The Object.setPrototypeOf() method allows dynamically assigning a new prototype to an existing object, altering its inheritance chain:
let animal = {
legs: 4
};
let creature = {
name: 'Rex'
};
Object.setPrototypeOf(creature, animal);
console.log(creature.name); // Rex
console.log(creature.legs); // 4
console.log(Object.getPrototypeOf(creature) === animal); // true
To avoid potential performance issues associated with Object.setPrototypeOf(), Object.create() is often preferred for creating a new object with a specific prototype at initialization:
let vehicle = {
wheels: 4
};
let car = Object.create(vehicle);
car.model = 'Sedan';
console.log(car.model); // Sedan
console.log(car.wheels); // 4
console.log(Object.getPrototypeOf(car) === vehicle); // true
Property Lookup Mechanism
When accessing a property on an object, the JavaScript engine initiates a search starting from the object instance. If the property is found locally, its value is returned. If not, the search traverses the prototype chain to the prototype object. This mechanism allows methods and properties to be shared efficiently across all instances.
While instances can read values from the prototype, they cannot directly modify them. If an instance defines a property with the same name as one on the prototype, the instance property takes precedence. This is known as shadwoing.
function Developer() {}
Developer.prototype.role = "Engineer";
Developer.prototype.sayRole = function() {
console.log(this.role);
};
let dev1 = new Developer();
let dev2 = new Developer();
dev1.role = "Architect";
console.log(dev1.role); // "Architect" (from instance)
console.log(dev2.role); // "Engineer" (from prototype)
In the example above, dev1.role shadows the prototype's role. The engine finds the instance property first and stops searching. However, dev2.role is not found on the instance, so it resolves to the prototype value.
Setting an instance property to null does not remove the shadow; it merely sets the instance value to null. To restore access to the prototype property, the instance property must be deleted.
let dev1 = new Developer();
let dev2 = new Developer();
dev1.role = "Architect";
console.log(dev1.role); // "Architect"
delete dev1.role;
console.log(dev1.role); // "Engineer" (prototype restored)
Checking Property Location
The hasOwnProperty() method (inherited from Object) determines if a property exists on the instence itself rather than on the prototype.
let dev1 = new Developer();
let dev2 = new Developer();
console.log(dev1.hasOwnProperty("role")); // false
dev1.role = "Architect";
console.log(dev1.role); // "Architect"
console.log(dev1.hasOwnProperty("role")); // true
console.log(dev2.hasOwnProperty("role")); // false
delete dev1.role;
console.log(dev1.hasOwnProperty("role")); // false