[[Prototype]]

Objects in JavaScript have an internal property, denoted in the specification as [[Prototype]], which is simply a reference to another object. Almost all objects are given a non-null value for this property, at the time of their creation.

Note: We will see shortly that it is possible for an object to have an empty [[Prototype]] linkage, though this is somewhat less common.

Consider:

  1. var myObject = {
  2. a: 2
  3. };
  4. myObject.a; // 2

What is the [[Prototype]] reference used for? In Chapter 3, we examined the [[Get]] operation that is invoked when you reference a property on an object, such as myObject.a. For that default [[Get]] operation, the first step is to check if the object itself has a property a on it, and if so, it’s used.

Note: ES6 Proxies are outside of our discussion scope in this book (will be covered in a later book in the series!), but everything we discuss here about normal [[Get]] and [[Put]] behavior does not apply if a Proxy is involved.

But it’s what happens if a isn’t present on myObject that brings our attention now to the [[Prototype]] link of the object.

The default [[Get]] operation proceeds to follow the [[Prototype]] link of the object if it cannot find the requested property on the object directly.

  1. var anotherObject = {
  2. a: 2
  3. };
  4. // create an object linked to `anotherObject`
  5. var myObject = Object.create( anotherObject );
  6. myObject.a; // 2

Note: We will explain what Object.create(..) does, and how it operates, shortly. For now, just assume it creates an object with the [[Prototype]] linkage we’re examining to the object specified.

So, we have myObject that is now [[Prototype]] linked to anotherObject. Clearly myObject.a doesn’t actually exist, but nevertheless, the property access succeeds (being found on anotherObject instead) and indeed finds the value 2.

But, if a weren’t found on anotherObject either, its [[Prototype]] chain, if non-empty, is again consulted and followed.

This process continues until either a matching property name is found, or the [[Prototype]] chain ends. If no matching property is ever found by the end of the chain, the return result from the [[Get]] operation is undefined.

Similar to this [[Prototype]] chain look-up process, if you use a for..in loop to iterate over an object, any property that can be reached via its chain (and is also enumerable — see Chapter 3) will be enumerated. If you use the in operator to test for the existence of a property on an object, in will check the entire chain of the object (regardless of enumerability).

  1. var anotherObject = {
  2. a: 2
  3. };
  4. // create an object linked to `anotherObject`
  5. var myObject = Object.create( anotherObject );
  6. for (var k in myObject) {
  7. console.log("found: " + k);
  8. }
  9. // found: a
  10. ("a" in myObject); // true

So, the [[Prototype]] chain is consulted, one link at a time, when you perform property look-ups in various fashions. The look-up stops once the property is found or the chain ends.

Object.prototype

But where exactly does the [[Prototype]] chain “end”?

The top-end of every normal [[Prototype]] chain is the built-in Object.prototype. This object includes a variety of common utilities used all over JS, because all normal (built-in, not host-specific extension) objects in JavaScript “descend from” (aka, have at the top of their [[Prototype]] chain) the Object.prototype object.

Some utilities found here you may be familiar with include .toString() and .valueOf(). In Chapter 3, we introduced another: .hasOwnProperty(..). And yet another function on Object.prototype you may not be familiar with, but which we’ll address later in this chapter, is .isPrototypeOf(..).

Setting & Shadowing Properties

Back in Chapter 3, we mentioned that setting properties on an object was more nuanced than just adding a new property to the object or changing an existing property’s value. We will now revisit this situation more completely.

  1. myObject.foo = "bar";

If the myObject object already has a normal data accessor property called foo directly present on it, the assignment is as simple as changing the value of the existing property.

If foo is not already present directly on myObject, the [[Prototype]] chain is traversed, just like for the [[Get]] operation. If foo is not found anywhere in the chain, the property foo is added directly to myObject with the specified value, as expected.

However, if foo is already present somewhere higher in the chain, nuanced (and perhaps surprising) behavior can occur with the myObject.foo = "bar" assignment. We’ll examine that more in just a moment.

If the property name foo ends up both on myObject itself and at a higher level of the [[Prototype]] chain that starts at myObject, this is called shadowing. The foo property directly on myObject shadows any foo property which appears higher in the chain, because the myObject.foo look-up would always find the foo property that’s lowest in the chain.

As we just hinted, shadowing foo on myObject is not as simple as it may seem. We will now examine three scenarios for the myObject.foo = "bar" assignment when foo is not already on myObject directly, but is at a higher level of myObject‘s [[Prototype]] chain:

  1. If a normal data accessor (see Chapter 3) property named foo is found anywhere higher on the [[Prototype]] chain, and it’s not marked as read-only (writable:false) then a new property called foo is added directly to myObject, resulting in a shadowed property.
  2. If a foo is found higher on the [[Prototype]] chain, but it’s marked as read-only (writable:false), then both the setting of that existing property as well as the creation of the shadowed property on myObject are disallowed. If the code is running in strict mode, an error will be thrown. Otherwise, the setting of the property value will silently be ignored. Either way, no shadowing occurs.
  3. If a foo is found higher on the [[Prototype]] chain and it’s a setter (see Chapter 3), then the setter will always be called. No foo will be added to (aka, shadowed on) myObject, nor will the foo setter be redefined.

Most developers assume that assignment of a property ([[Put]]) will always result in shadowing if the property already exists higher on the [[Prototype]] chain, but as you can see, that’s only true in one (#1) of the three situations just described.

If you want to shadow foo in cases #2 and #3, you cannot use = assignment, but must instead use Object.defineProperty(..) (see Chapter 3) to add foo to myObject.

Note: Case #2 may be the most surprising of the three. The presence of a read-only property prevents a property of the same name being implicitly created (shadowed) at a lower level of a [[Prototype]] chain. The reason for this restriction is primarily to reinforce the illusion of class-inherited properties. If you think of the foo at a higher level of the chain as having been inherited (copied down) to myObject, then it makes sense to enforce the non-writable nature of that foo property on myObject. If you however separate the illusion from the fact, and recognize that no such inheritance copying actually occurred (see Chapters 4 and 5), it’s a little unnatural that myObject would be prevented from having a foo property just because some other object had a non-writable foo on it. It’s even stranger that this restriction only applies to = assignment, but is not enforced when using Object.defineProperty(..).

Shadowing with methods leads to ugly explicit pseudo-polymorphism (see Chapter 4) if you need to delegate between them. Usually, shadowing is more complicated and nuanced than it’s worth, so you should try to avoid it if possible. See Chapter 6 for an alternative design pattern, which among other things discourages shadowing in favor of cleaner alternatives.

Shadowing can even occur implicitly in subtle ways, so care must be taken if trying to avoid it. Consider:

  1. var anotherObject = {
  2. a: 2
  3. };
  4. var myObject = Object.create( anotherObject );
  5. anotherObject.a; // 2
  6. myObject.a; // 2
  7. anotherObject.hasOwnProperty( "a" ); // true
  8. myObject.hasOwnProperty( "a" ); // false
  9. myObject.a++; // oops, implicit shadowing!
  10. anotherObject.a; // 2
  11. myObject.a; // 3
  12. myObject.hasOwnProperty( "a" ); // true

Though it may appear that myObject.a++ should (via delegation) look-up and just increment the anotherObject.a property itself in place, instead the ++ operation corresponds to myObject.a = myObject.a + 1. The result is [[Get]] looking up a property via [[Prototype]] to get the current value 2 from anotherObject.a, incrementing the value by one, then [[Put]] assigning the 3 value to a new shadowed property a on myObject. Oops!

Be very careful when dealing with delegated properties that you modify. If you wanted to increment anotherObject.a, the only proper way is anotherObject.a++.