Question

I've been experimenting with prototypal inheritance, as in the following snippet.

function extend(c, p) {
    function f() { this.constructor = c; }
    f.prototype = p.prototype;
    c.prototype = new f();
}

function Parent() {}
function Child() {}

extend(Child, Parent);

var p = new Parent();
var c = new Child();

// Child.prototype.say = function () { alert("child"); };
Parent.prototype.say = function () { alert("parent"); };

p.say();
c.say();

When running this script, two alerts displaying parent will show up.

If I uncomment the commented line, however, the first alert displays parent, while the second displays child.
At a first glance, it was quite unexpected. It would seem that Parent.say would override Child.say, since it is set later on.

From my understanding, since Child.prototype is an instance of an object, an instance of f, all properties set on this prototype are set directly on that particular instance of f.

When calling c.say, the following happens:

  1. If say was set directly on c, call it. It is never set directly on the instance, so, skip to 2.

  2. Look for say in Children.prototype, the instance of f. Again, look for a property set directly on the instance of f. If the line is uncommented, say is found here, and the search stops.

  3. Look for say in f.prototype, which is Parent.prototype. This is where say is found, when the line remains commented.

Q: Did I understand correctly how JavaScript looks for the property? If this is the case, that would explain why the child property isn't overridden by the parent property, when the line is uncommented.

Was it helpful?

Solution

Did I understand correctly how JavaScript looks for the property?

Essentially, yes. But it's important to note that the underlying prototype of an object is set by the new operation to point to the object referenced by the constructor function's prototype property, at which point if you pointed the constructor's prototype property at a completely different object, it wouldn't have any effect on existing children. The children refer to the object, not the property.

So in general, property lookup works like this:

  • Does the property exist on the object itself? If so, use its value.
  • No, does the property exist on the object's underlying prototype? If so, use its value.
  • Does it exist on its prototype's prototype? If so, use the value.
  • And so on until we find the property, or run out of prototypes.

Let's throw some ASCII-Art at what you're building there, just for fun:

+-----------+
| Parent    |<---------------------------+
+-----------+                            |
| prototype |---------->+-------------+  |
+-----------+      +--->|  (object)   |  |
                   | +->+-------------+  |
                   | |  | constructor |--+       +------------------+
                   | |  | say         |--------->|    (function)    |
                   | |  +-------------+          +------------------+
                   | |                           | alert("parent"); |
                   | |                           +------------------+
                   | |
                   | +--------------------------------------------------+
                   |                                                    |
                   +-----------------------+                            |
+-----------+                              |                            |
| Child     |<---------------------------+ |                            |
+-----------+           +-------------+  | |                            |
| prototype |------+--->|  (object)   |  | |                            |
+-----------+      |    +-------------+  | |                            |
                   |    | constructor |--+ |                            |
                   |    | __proto__   |----+     +------------------+   |
                   |    | say         |--------->|    (function)    |   |
                   |    +-------------+          +------------------+   |
                   |                             | alert("child");  |   |
+-----------+      |                             +------------------+   |
|     c     |      |                                                    |
+-----------+      |                                                    |
| __proto__ |------+                                                    |
+-----------+                                                           |
                                                                        |
+-----------+                                                           |
|     p     |                                                           |
+-----------+                                                           |
| __proto__ |-----------------------------------------------------------+
+-----------+

...where __proto__ is the hidden property that represents the object's link to its prototype. (Some engines actually expose this, and there's a proposal to add that to the standard.)

As you can see, Child.prototype and the c instance's __proto__ both point to the same object (and similarly for Parent.prototype and p's __proto__).

The reason I make the distinction in my first paragraph above is this:

function Foo() {
}
Foo.prototype.a = 1;
var f1 = new Foo();
Foo.prototype = {b: 2}; // Putting an entirely new object on `prototype`, not just modifying it
var f2 = new Foo();
console.log(f1.a); // "1"
console.log(f1.b); // "undefined"
console.log(f2.a); // "undefined"
console.log(f2.b); // "2"

f1 and f2 end up having completely different prototypes, and by the end of the above, f1's __proto__ no longer refers to the same object Foo.prototype refers to.

For this reason, except in very specific situations (like your extend function), I strongly recommend not assigning new objects to the prototype property of a constructor function, because it can get really confusing. :-) Your extend function is, again, an exception.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top