Define the prototype of a function using object.create ()

advertisements

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,


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(...).