0

I'm looking at at two examples from MDN about inheritance and prototypes. There seems to be some conflict in my understanding given these two examples—they seem contradictory:

var a = {a: 1};
//inheritance looks like: a ---> Object.prototype ---> null
var b = Object.create(a);
//inheritance looks like: b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)

Makes sense so far, but then on another page, learning about the .call() method:

function Product(name, price) {
  this.name = name;
  this.price = price;

  if (price < 0) {
    throw RangeError('Cannot create product ' +
                      this.name + ' with a negative price');
  }

  return this;
}

function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}

Food.prototype = Object.create(Product.prototype);

function Toy(name, price) {
  Product.call(this, name, price);
  this.category = 'toy';
}

Toy.prototype = Object.create(Product.prototype);

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

Isn't Food's prototype now going to be the Product's prototype? i.e Function.prototype?

I was expecting:

Food.prototype = Object.create(Product)

Does this have to do with the fact that it's a function?

Thanks,

2
  • "i.e Function.prototype?" The prototype property of a function is initially a plain Object. The property is used for inheritance rather than the function itself so the various types don't have to inherit Function behavior. Commented Jul 2, 2015 at 4:05
  • Constructor.prototype is the reference to the object which should be inherited by instances of Constructor whereas obj.prototype is just a regular property just like any other, for example, a.prototype and b.prototype in your code are both undefined. Commented Jul 2, 2015 at 9:55

2 Answers 2

1

You do seem to have a misunderstanding of the second example. In particular, I think you may have some confusion between "constructor" and "prototype". I'll try to explain by going through the example.

First of all, the example declares a function, Product. This is an ordinary function, but the fact that it references this suggests that it's intended to be used as a constructor. Note that the return this statement is superfluous, as a function's return value is ignored when invoked as a constructor (ie with new).

Product is itself an object - it's a function, and functions are objects. All objects have a prototype, which is different from what you get with Product.prototype - you can access it with Object.getPrototypeOf(Product), and you'll find it to be equal to Function.prototype. All objects also have a contructor, which can be obtained with Product.constructor. Since product is a function, its constructor is Function. The constructor is always a function.

Then, what's the prototype attribute? Not all objects have this - only functions do. It's not the prototype of Product itself. It's the prototype of any objects created via new Product(name,price) (ie, Object.getPrototypeOf(new Product(name,price)) == Product.prototype). If you want to think of Product as a class, then the class's prototype is Product.prototype. And what's the constructor of Product.prototype? It's easy to confirm that Product.prototype.constructor == Product.

So, what would happen if you did Food.prototype = Object.create(Product), instead of Food.prototype = Object.create(Product.prototype)?

  1. Object.create(Product)' creates a new object and sets its prototype toProduct. You can verify thatObject.getPrototypeOf(Object.create(Product)) == Product`.
  2. Then you set Food.prototype to this new object.
  3. Now, when you call var cheese = new Food('feta', 5), you're invoking Food as a constructor. Thus, a new object is created, and Food.prototype is set as its prototype, ie the new object's prototype is an object whose prototype is Product.
  4. Next, Food is called with this new object as its this argument. Food then passes the this argument on to Product. So far, so good, right?
  5. Here's the catch. You'll find that Food.constructor == Function. Huh? What happened to Product? Well, your prototype chain reaches back to Product, sure, and the prototype of Product is Function.prototype, and nowhere along that chain was the constructor overridden.

So... in some weird way, it almost looks as though cheese is a function? Well, it's not (you can't call it, for example), but it won't inherit any attributes or methods you put in Product.prototype (though it will inherit ones put in Product). The bad thing is that it will also inherit any methods defined in Function - for example, the call method itself. Worse, trying to use this (eg cheese.call({})) will raise an error, since cheese is not itself a function.

Thus, the correct way is to use Food.prototype = Object.create(Product.prototype) (or equivalently, Food.prototype = new Product).


The key point here is probably that Product.prototype is not the prototype of Product. Rather, it's the prototype of new objects created by invoking new Product(...).

Sign up to request clarification or add additional context in comments.

4 Comments

Really appreciate your answer; I think it clarified a lot—especially the bit about .prototype property having to do with how new objects are created using the function it belongs to. I do have a few questions, though: In 5), when you say that the prototype of product is Function, did you mean to say it's Function.prototype? also, since Product.prototype is empty, what good does it do? Would the chain now look like: Food ---> Product.prototype ---> Object.prototype ---> null?
Yes, the prototype of Product is Function.prototype. The prototype of Food.prototype is Product.prototype, which is empty in the example; however, adding something to Product.prototype makes it appear in Food.prototype as well, even if it's added much later.
One more thing: I was recursively calling Object.getPrototypeOf to see what the chain looked like. I was surprised that I had to call it a total of 4 times to reach null. Why is that? I thought the chain would look like: cheese ---> {} ---> Object.prototype ---> null That's only 3, what's the missing link in the chain? When I use safari, I see Product show up as the prototype of cheese. Is it because it's the constructor of Product.prototype?
Regarding the first question, the chain is cheese --> Food.prototype --> Product.prototype --> Object.prototype --> null. Regarding the second, I think that's just an artifact of how Safari prints objects, by using the constructor name as if it were a class name. If you add a line Food.prototype.contructor = Food, Safari will print Food when you ask for Object.getPrototypeOf(cheese). It's actually printing Food.prototype, which you can verify by adding some data members to Food.prototype and clicking the disclosure triangle beside the result of Object.getPrototypeOf(cheese).
0

Either way works. Two different objects can have the same prototype. You can set one prototype, say Foo.prototype, to be the same object as Bar.prototype. So you can set it to either the prototype of an object, or an instance of the object. You can do either one.

The difference is, sometimes you want to have one object, Foo, inherit methods from another object, Bar, so you make the Foo.prototype equal to Bar.prototype. So every method on the prototype of Bar are now available as methods on Foo. But let's you want methods to be added to Bar's prototype but not to Foo's. In that case, you could do what you suggested, and make Bar's prototype an instance of the Foo object instead of it's prototype. That way, when you add method's to Bar's prototype, you're just adding it to that one instance of Foo, instead of the Foo prototype which affects ALL Foo instances.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.