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 toFoo.prototype - I set
Bar.prototypetoFoo, rather than setting it tonew Foo()orObject.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!
3 Answers 3
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:
- The prototypal pattern of prototypal inheritance
- 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:
Comments
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.
1 Comment
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.
9 Comments
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
Foo, not the instances, which is what settingprototypedoes. When you setBar.prototypeasFoo, theFooconstructor'sreportproperty is being referencedFoo.report = ...is the same asFoo.report.constructor = ...?new(at least the way you have it set up and what you'd usually expect).Foois just an object (a function), and it has properties. But those properties aren't copied to instances when you usenew Foo(). The properties that are copied are fromFoo.prototype. And by "copy", I really mean "reference". You're essentially doingBar.prototype = { report: func... };Foois just an object that can be invoked...it has properties. By default,Foohas no properties (like an empty object). Then, you set thereportproperty. 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 yourfobject because you didn't set thereporton theFooprototype, soFooinstances won't "inherit" that propertyprototypeproperties 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 jsfiddle.net/7y9Fg . You should see thatFooinstances don't have thereportproperty (becausereportisn't in theFoo.prototype). ButBarinstances do