1

I am reading this piece of code

// Shape - superclass
function Shape() {
  this.x = 0;
  this.y = 0;
}

// superclass method
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); // call super constructor.
}

// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

rect instanceof Rectangle; // true
rect instanceof Shape; // true

rect.move(1, 1); // Outputs, 'Shape moved.'

I got really confused by this snippet

Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

Why don't we use Rectangle.prototype = Shape.prototype, anything special that Object.create() does? and what if Rectangle.prototype.constructor = Rectangle; is not called?

Blake
  • 7,367
  • 19
  • 54
  • 80
  • `Why don't we use Rectangle.prototype = Shape.prototype` A Rectangle is a Shape but not every Shape is a Rectangle. Changing Rectangle.prototype would change Shape.prototype. Constructor is repaired for consistency in case someone wants to use it (and console logging in Chrome). More on constructor, prototype, inheritance, mix ins and so on here: http://stackoverflow.com/a/16063711/1641941 – HMR Dec 05 '14 at 02:40

2 Answers2

2

If you do Rectangle.prototype = Shape.prototype then any modification that you will perform on Rectangle.prototoype will reflect on Shape.prototype because they are the same object.

To avoid this, we create a new object that has it's prototype link pointing to Shape.prototype using Object.create(Shape.prototype).

As for Rectangle.prototype.constructor = Rectangle;, it's to make sure that new Shape().constructor points to Rectangle, not Shape.

The native prototype of a function has a constructor property that points to that function.

e.g.

function Rectangle() {}

Rectangle.prototype.constructor === Rectangle; //true

Now, when we do Rectangle.prototype = ..., we break that reference and need to fix it afterwards.

plalx
  • 42,889
  • 6
  • 74
  • 90
  • Hi, thanks for your reply. yes.. I am always confused about under what circumstance a variable is a reference (as in this case Rectangle.prototype) and when it is a copy. do you have any insights on this or can you point me to some good articles? – Blake Dec 04 '14 at 22:39
  • @Blake Objects are referenced, but primitive values are copied (no choice because they are immutable). – plalx Dec 04 '14 at 22:49
0

If you’d just set Rectangle.prototype to Shape.prototype, then both Rectangle and Shape would share the same object as prototype. That would mean, that everything you add to Rectangle.prototype would also be available on Shape.prototypeRectangle would no longer inherit from Shape.

Object.create(parent) creates a child object that inherits from the parent object passed in. This way, all properties of the parent are also available to the child, while properties on the child don’t affect the parent.

To answer your question (“Why we need to define constructors in JavaScript?”) – we don’t need to. You could also just use plain objects:

// Shape - superclass
function Shape() {
  return {
    x: 0, 
    y: 0,
    // superclass method
    move: function(x, y) {
      this.x += x;
      this.y += y;
      console.info('Shape moved.');
    }
  };
}

// Rectangle - subclass
function Rectangle() {
  var rectangle = Shape();
  // rectangle-specific things could be added here
  return rectangle;
}

var rect = Rectangle();

rect instanceof Rectangle; // false, instanceof does not work here
rect instanceof Shape; // false

rect.move(1, 1); // Outputs, 'Shape moved.'
David Aurelio
  • 484
  • 2
  • 11
  • Your non-constructor sample is not so pure and quite misleading. Here's what you should be doing `var shape = { ... }; var rectangle = Object.create(shape); //add rectangle stuff var r1 = Object.create(rectangle);` – plalx Dec 04 '14 at 22:17
  • Please forgive me for writing examples that are not so pure. I just wanted to demonstrate that we’re not bound to “class-like” inheritance in JavaScript. Pureness is not my primary concern. I don’t agree that the example is misleading, though. `Shape()` returns a fresh shape on every call. Nothing is gained by inheriting from it with `Object.create()`. It’s entirely possible and safe to extend that object directly. – David Aurelio Dec 04 '14 at 22:22
  • Well, it's misleading because there's a widespread naming convention which states that upper-cased functions should be constructors, but even if you do fix that, your code is still inefficient in terms of memory because you aren't sharing anything through prototypes. It's also inefficient in terms of execution e.g. you will have to mutate the newly created shape object everytime to create to rectangle instances. All these are solved by using `Object.create` like I suggested, which is the standard way of avoiding constructors. – plalx Dec 04 '14 at 22:48
  • You could share the methods by not creating new ones for every object, and rather just assigning methods by reference. I didn't do it to keep the example simple. This way, we would *save memory* by not allocating prototypes. Mutation: Calling super constructors on newly created instances also mutates them. The code is going through the same mutations each time, JITs optimize for this. Btw, using `Object.create()` here wouldn’t save any memory, since the example doesn't use prototypal inheritance. `Object.create()` does not have any benefit at all for my example. – David Aurelio Dec 04 '14 at 23:17
  • I made no reference to constructor/initialization's mutation and that's unavoidable. What I'm saying is that with your implementation every new shape must be mutated to become a rectangle and that's unecessary. Yes, `Object.create` would save memory because the default values and functions wouldn't have to be copied for every instance. Just try both approaches and do a profiling, it will be obvious with multiple instances. Finally, some engines like V8 are optimizing expando properties set in constructors. – plalx Dec 04 '14 at 23:41
  • `// rectangle-specific things could be added here` this is what I would be concerned about. – plalx Dec 04 '14 at 23:47
  • That’s funny, when using constructors mutation is unavoidable, but when using plain object it’s a problem? Sure! I suggest *you* do some profiling to support your claims. Engines like V8 don’t care about properties set in constructors, they care about many objects going through the same mutations and optimize for that. Setting properties in constructors is just one case. – David Aurelio Dec 05 '14 at 06:27
  • There’s no case for `Object.create` in an example intended to show OO without prototypes. The cost of shared methods without prototypes is exactly one reference. That’s no problem at all on today’s devices. And talking about negligible performance differences: Not sharing through the prototype chain leads to faster lookups. – David Aurelio Dec 05 '14 at 06:29
  • Ok, I made some tests and it seems that there's no good reasons for not using constructors. You can clearly see that they are better optimized (at least in V8). `Object.create` seems to be the worst approach in call cases and what you suggest seems to be faster in IE11 (with function caching however). http://jsperf.com/various-oo-approaches – plalx Dec 05 '14 at 13:42
  • The only advantage of `Object.create` over the no-prototype sample is that it will take slightly less memory if some default/constant primitive values can be shared like in the test (`defaultPrimitive`), but you get that for free using constructors and `new`. – plalx Dec 05 '14 at 14:16
  • Cool that you’ve actually measured. To get back on topic: I use constructor-based OO all the time, I’m not advocating against it. I just wanted to show that we’re not bound to a single OO pattern in JS – David Aurelio Dec 05 '14 at 20:33