1

Before I learned about Object.create(), and when I understood prototypes even less than I do now, I wrote some code that looked like this:

var Foo = function() {
    this.report('Foo');
};

Foo.report = function(i) {  // You read that right; it's not 'Foo.prototype.report'
    alert('report: ' + i);
};

var Bar = function() {
    this.report('Bar');
};

Bar.prototype = Foo;  // Right; it's not 'new Foo()'

var b = new Bar();
b.report('b');

Strangely, this actually works, or at least it worked the way I expected it to: it alerts 'Bar' and then 'b'. But if I try to use Foo() directly, it fails:

var f = new Foo();
f.report('f');

The browser tells me that the object has no method report(). Why does it work for 'b' but not for 'f'? Further, if I set Bar.prototype = new Foo() (without changing Foo.report to Foo.prototype.report), then again 'b' works, but the browser says there's no report() method for 'f'. But if I add report() to Foo.prototype instead of just adding it to Foo, then everything works perfectly. And naturally, if I set Bar.prototype = Object.create(Foo.prototype), everything works also. But the way the misuses partially work and fail baffles me completely. Can someone help me to understand exactly what's going on when I do these things? Specifically, what's going on when:

  • I add a function to Foo, rather than adding it to Foo.prototype
  • I set Bar.prototype to Foo, rather than setting it to new Foo() or Object.create(...)

Resig comes close to this in Slide 76, but he doesn't explain it. I know that this is not the way to use prototypes, but I sense that understanding this behavior will shed some light on js prototyping. I thought I understood it!

SaganRitual
  • 3,143
  • 2
  • 24
  • 40
  • 2
    You're setting a property on the *constructor* `Foo`, not the *instances*, which is what setting `prototype` does. When you set `Bar.prototype` as `Foo`, the `Foo` constructor's `report` property is being referenced – Ian Aug 10 '13 at 05:14
  • @Ian So `Foo.report = ...` is the same as `Foo.report.constructor = ...`? – SaganRitual Aug 10 '13 at 05:15
  • 1
    I don't think so. Maybe I shouldn't have said "constructor". What I meant by "constructor" is just that it can be used as creating instances by using `new` (at least the way you have it set up and what you'd usually expect). `Foo` is just an object (a function), and it has properties. But those properties aren't copied to instances when you use `new Foo()`. The properties that are copied are from `Foo.prototype`. And by "copy", I really mean "reference". You're essentially doing `Bar.prototype = { report: func... };` – Ian Aug 10 '13 at 05:17
  • @Ian Ok, so you're saying that when I set `Bar.prototype = Foo`, I'm setting the prototype to the *function* `Foo`, rather than an object that was constructed using `Foo`. Right? That makes a lot of sense. Still not clear why it didn't work for my 'f' object. – SaganRitual Aug 10 '13 at 05:23
  • 1
    Yeah I think so. I'm a little confused with the wording, but I think you understand. Even though it's declared as a function, `Foo` is just an object that can be invoked...it has properties. By default, `Foo` has no properties (like an empty object). Then, you set the `report` property. So if you look at my last comment (not sure if you saw the edits), you'll see what it's basically doing (like the last sentence). And remember, it didn't work for your `f` object because you didn't set the `report` on the `Foo` **prototype**, so `Foo` instances won't "inherit" that property – Ian Aug 10 '13 at 05:27
  • So properties don't get copied to the new object when I call `new`! I'm sure that's in some doc somewhere, but it's news to me. That makes a ton of sense. ***Edit*** -- no, that can't be right, because then 'b' wouldn't be able to call `report()` when I use `b = new Foo()` – SaganRitual Aug 10 '13 at 05:31
  • 1
    Function **properties** aren't inherited by instances. Function `prototype` properties **are**. Your last comment seemed to contradict itself, but maybe I'm misreading. I'm not sure if it would help, but take a look at http://jsfiddle.net/7y9Fg/ . You should see that `Foo` **instances** don't have the `report` property (because `report` isn't in the `Foo.prototype`). But `Bar` **instances** do – Ian Aug 10 '13 at 05:46
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35183/discussion-between-greatbigbore-and-ian) – SaganRitual Aug 10 '13 at 06:09

3 Answers3

2

I understand your problem. The slide you mentioned - John Resig's 76th slide, has already been explained in the following answer:

https://stackoverflow.com/a/17768570/783743

The problem in your case is a classic problem poeple face when first learning JavaScript; and this problem arises not because of your ineptitude to understand prototypal inheritance but because of the way prototypal inheritance has been portrayed in JavaScript.

The Two Faces of Prototypal Inheritance

Prototypal inheritance can be implemented in one of two ways. Hence prototypal inheritance is analogous to a coin. It has two faces:

  1. The prototypal pattern of prototypal inheritance
  2. The constructor pattern of prototypal inheritance

The first pattern is the common or the true pattern of prototypal inheritance. Languages like Self and Lua employ the prototypal pattern of prototypal inheritance.

The second pattern was designed to make prototypal inheritance look like classical inheritance. It's only used in JavaScript and it hides the way prototypal inheritance works making it difficult to understand it.

I've discussed prototypal inheritance in depth in my article on Why Prototypal Inheritance Matters and I recommend that you read it carefully.

Understanding Prototypal Inheritance

Prototypal inheritance is all about objects inheriting from other objects. There are no classes in prototypal inheritance. Only objects. For example, consider:

var boy = {};
var bob = Object.create(boy);

In the above example the object bob inherits from the object boy. In simple English:

Bob is a boy.

In classical inheritance you would write the above as:

class Boy {
    // body
}

Boy bob = new Boy;

As you can see prototypal inheritance is very flexible. In prototypal inheritance all you need are objects. Objects behave as both classes and as instances. An object which behaves as a class is called a prototype. Hence boy is a prototype of bob.

Constructing an Object from a Prototype

Now consider we have an object called rectangle:

var rectangle = {
    width: 10,
    height: 5,
    area: function () {
        return this.width * this.height;
    }
};

We may use rectangle as an instance as follows:

console.log(rectangle.area());

On the other hand we may also use rectangle as a prototype:

var rect2 = Object.create(rectangle);
rect2.width = 15;
rect2.height = 6;
console.log(rect2.area());

However it's a pain to keep defining the width and height on every object that inherits from rectangle. Hence we create a constructor:

rectangle.create = function (width, height) {
    var rect = Object.create(this);
    rect.height = height;
    rect.width = width;
    return rect;
};

Now you can create instance of rectangle as follows:

var rect2 = rectangle.create(15, 6);
console.log(rect2.area());

This is the prototypal pattern of prototypal inheritance because in this method the focus is on the prototype and not on the constructor function create.

The Constructor Pattern

The same thing can be done in JavaScript using the constructor pattern as follows:

function Rectangle(width, height) {
    this.height = height;
    this.width = width;
}

var rectangle = Rectangle.prototype;

rectangle.width = 10;
rectangle.height = 5;

rectangle.area = function () {
    return this.width * this.height;
};

var rect2 = new Rectangle(15, 6);
console.log(rect2.area());

The problem with the constructor pattern is that the focus is on the constructor instead of the prototype. Hence people think that rect2 is an instance of Rectangle which is false. In JavaScript objects inherit from other objects and not from constructors.

The object rect2 is an instance of Rectangle.prototype at the time rect2 was created. It's not an instance of Rectangle.

Your Problem

In your question you define Foo as follows:

var Foo = function() {
    this.report('Foo');
};

Foo.report = function(i) {
    alert('report: ' + i);
};

Hence when you create an object from Foo using new (i.e. new Foo) the object inherits from Foo.prototype. It doesn't inherit from Foo. Hence the object doesn't have any property called report and thus throws an error.

In your question you also define Bar:

var Bar = function() {
    this.report('Bar');
};

Bar.prototype = Foo;  // Right; it's not 'new Foo()'

Here the prototype of Bar is Foo. Hence objects created from Bar will inherit from Bar.prototype or Foo. Hence those objects inherit the report function from Foo.

I know. It's very confusing, but that's the way prototypal inheritance is implemented in JavaScript. Don't forget to read my blog post:

Why Prototypal Inheritance Matters

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
1

You're setting Bar.prototype to a given object (namely the function Foo), so that object is the prototype of any instances of Bar, and you're adding methods to that prototype, so those methods are available on all instances of Bar.

This is certainly atypical — usually we don't use functions (especially constructors) as prototypes — but functions (including constructors) are objects, and are therefore eligible to be prototypes, so from a language standpoint, there's absolutely nothing wrong with it. I wouldn't call it a "misuse", unless all you mean is that it's not what you had intended to do.

ruakh
  • 175,680
  • 26
  • 273
  • 307
0

You're not having any problem understanding prototypes. You're having a problem understanding the difference between f = Foo and f = new Foo().

When you set f = Foo, you're setting 'f' equal to the object called Foo. Naturally, if you've added a report() method to Foo, then you can call f.report() with no problem. But note that you have no access to Foo's prototype. If you set Foo.prototype.announce = function() {...};, you will not be able to call f.announce()--there is no f.announce() to call.

When you set f = new Foo(), you're creating a new object, and using the Foo() function to construct it, that is, running the Foo() function with the new object as Foo's this. You have access to Foo's prototype, but no access to Foo's properties. You can call f.announce(), but there is no f.report().

Similarly, when you set Bar.prototype = Foo, you're setting Bar.prototype equal to the object called Foo. Because you set b = new Bar(), 'b' has access to Bar's prototype, so you can call b.report(), because Bar's prototype (the object called Foo) has a report() function in it. But you have no access to anything in Foo's prototype, for the reasons explained above.

So by now it should be obvious what happens when you set Bar.prototype = new Foo(): Bar.prototype has access to Foo's prototype (but not any of the methods of the Foo function object, for reasons explained above). So you can call any method either in Bar's prototype or Foo's prototype.

And of course, Object.create() is now the preferred method for creating objects, rather than new.

SaganRitual
  • 3,143
  • 2
  • 24
  • 40
  • 2
    People will think that you're crazy if you talk to yourself in first person. Not that it's any of my business but your answer does not specifically answer your own question. To a third person your answer has absolutely nothing to do with your question. Your answer is a reflection about what you have understood and not what the question demands. This is the classic [xy problem](http://meta.stackexchange.com/q/66377/215154 "What is the XY problem? - Meta Stack Overflow") related to questions and answers on StackOverflow. Please consider making your answer more specific. – Aadit M Shah Aug 10 '13 at 18:25
  • Hmm, very good points. I guess the real problem is that I asked the wrong question to begin with. I changed the title after posting this answer. I don't know if it's really XY, because it wasn't so much that I was asking about an attempted solution; I just thought I was having prototype issues. And in fact it *is* related to prototypes, just not in the way I thought at first. Maybe I should delete the question entirely? – SaganRitual Aug 10 '13 at 18:34
  • @AaditMShah Actually, my answer *does* answer the questions: *Why does it work here but not there*, and *Specifically, what's going on when...* But I'm definitely open to suggestions and corrections from more experienced users. Advice? – SaganRitual Aug 10 '13 at 18:37
  • I am afraid Ian's comments have done more harm than good. Clearly you seem to have gained a greater understanding of how constructors work in JavaScript. However your focus has now shifted onto constructors instead of prototypes. JavaScript is a prototypal object-oriented programming language. Hence the focus should be on prototypes and not on constructors. I strongly recommend you to [avoid using the `new` keyword](http://aaditmshah.github.io/why-prototypal-inheritance-matters/#stop_using_the_new_keyword). Also do read my [answer to your question](http://stackoverflow.com/a/18164910/783743). – Aadit M Shah Aug 11 '13 at 03:33
  • 1
    @AaditMShah More harm than good? Hmm, explaining how it works is harm to you? I didn't suggest anything, I explained – Ian Aug 12 '13 at 15:28
  • 1
    @Ian Your comments and our subsequent chat helped me to formulate the answer that I posted. Thanks very much. – SaganRitual Aug 12 '13 at 16:29
  • @GreatBigBore You're very welcome! AaditMShah seems to think my explanation was harmful, so I wanted to understand why. – Ian Aug 12 '13 at 16:35
  • @Ian I'm not suggesting that your comments are misleading. On the contrary your comments are accurate. However after reading your comments the OP seems to have concluded that he has a problem understanding how `new` works instead of how prototypes work. The focus has shifted from prototypes to constructors, which as I mentioned before makes it more difficult to understand prototypal inheritance. In the end when you construct an object from a function using `new` in JavaScript the newly created object inherits from the `prototype` of the function. Not the function itself. That must be stressed. – Aadit M Shah Aug 12 '13 at 16:37
  • 1
    @AaditMShah `In the end when you construct an object from a function using new in JavaScript the newly created object inherits from the prototype of the function. Not the function itself.` - that's like the main thing I was trying to stress, but I guess I was having a hard time explaining or getting through to the OP. Anyways, your explanations of prototypal inheritance are always great – Ian Aug 12 '13 at 16:44