2012-11-07

Property assignment and the prototype chain

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

  1. JavaScript: __proto__
  2. What object is not an instance of Object?
  3. Object properties in JavaScript
  4. JavaScript’s strict mode: a summary
  5. Properties in JavaScript: definition versus assignment

No comments: