This blog post examines how the prototype chain of an object affects assignments to its properties. It provides a more detailed look than the previous, more comprehensive, blog post “
Properties in JavaScript: definition versus assignment”.
The prototype chain
Each object starts a
prototype chain of one or more objects. All properties in that chain are readable via that object. For example:
> var proto = { foo: 1 };
> var obj = { __proto__: proto, bar: 2 };
> obj.foo
1
> obj.bar
2
We used the special property
__proto__ [1] to create the chain (which is not yet supported by all browsers).
The prototype chain of
obj comprises three objects: It starts with
obj, continues with
proto and ends with
Object.prototype.
Object.prototype is the constructor prototype of
Object and a member of most (but not all
[2]) prototype chains:
> Object.prototype.isPrototypeOf({})
true
> Object.prototype.isPrototypeOf([])
true
> Object.prototype.isPrototypeOf(new Date())
true
It is also the final member:
> Object.getPrototypeOf(Object.prototype)
null
Object.prototype gives objects many standard methods such as
toString() and
hasOwnProperty().
Assigning to properties
If you assign to a property, you will always only modify the first member in the prototype chain: If the property is there, already, it will be changed. If not, it will be created:
> obj.foo = 3;
> obj.foo
3
> obj.hasOwnProperty("foo")
true
> proto.foo
1
The idea is that a prototype introduces an initialization value that is the same for all instances. If assigning to the property of an instance could change that value then all instances would have different initialization values afterwards. To prevent this from happening while still allowing changing the initial value, assignment only lets you change an existing own property. If no such property exists, it is created automatically.
Accessors and the prototype chain
An accessor property
[3] in the prototype chain prevents an own property from being created in the first prototype chain member. You can provide both a getter and a setter:
var obj = {
__proto__: {
get foo() {
return 1;
},
set foo(x) {
console.log("Setter called: "+x);
}
}
};
Then assigning to
foo calls the setter instead of creating an own property and reading it calls the getter:
> obj.foo = 2;
Setter called: 2
> obj.foo
1
You can prevent the property assignment by only providing a getter:
var obj = {
__proto__: {
get foo() {
return 1;
}
}
};
The assignment quietly fails in non-strict mode and throws an error in strict mode
[4]:
> (function () { "use strict"; obj.foo = 2; }());
TypeError: Cannot set property foo of obj which has only a getter
Read-only properties in the prototype chain
If the first prototype chain object inherits a read-only property, one cannot change that property via assignment. For example, given the following code.
var proto = Object.defineProperty({},
"foo",
{
value: 1,
writable: false
});
var obj = { __proto__: proto };
It is impossible to assign to
obj.foo:
> (function () { "use strict"; obj.foo = 2; }());
TypeError: obj.foo is read-only
This is in line with how a getter-only property works and conforms to the metaphor introduced above: again, the prototype property is shared state, but this time, we want to prevent its values from being changed. If you wanted to create an own property
foo in
obj, you could do so via
Object.defineProperty() and
Object.defineProperties() [5].
References
- JavaScript: __proto__
- What object is not an instance of Object?
- Object properties in JavaScript
- JavaScript’s strict mode: a summary
- Properties in JavaScript: definition versus assignment