Reputation: 3203
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:
Foo
, rather than adding it to Foo.prototype
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!
Upvotes: 1
Views: 164
Reputation: 74204
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.
Prototypal inheritance can be implemented in one of two ways. Hence prototypal inheritance is analogous to a coin. It has two faces:
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.
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
.
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 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
.
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
Upvotes: 2
Reputation: 3203
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
.
Upvotes: 0
Reputation: 183270
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.
Upvotes: 1