4

I'm sorry, but I really don't know how to specify more exactly the question. The most common way of inheritance can be seen in this example:

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 = new Shape();

var rect = new Rectangle();

rect.move(2, 1);

I am concerned about the following code piece:

//subclass extends superclass
Rectangle.prototype = new Shape();

Isn't it better to connect directly to the parent prototype property.

Rectangle.prototype = Shape.prototype;

As much as I understand this is the case with all objects inheriting directly from Object. Their prototype is connected not to some instance of Object but to its prototype property?

In the above case Shape.call(this); will copy the properties(or something like this) and we need to inherit only methods which are so and so in the Shape.prototype. Or may be I am missing something?

Nikolay
  • 1,111
  • 2
  • 12
  • 16

2 Answers2

4

I am concerned about the following code piece:

//subclass extends superclass
Rectangle.prototype = new Shape();

You should be, that's a common pattern, but not a very good one, because then you're using Shape both to create prototypes and to initialize instances. It should do one or the other. (Consider, for instance, what you'd do if you needed to give Shape arguments? What args would you use creating the Rectangle.prototype?)

But you don't want Rectangle.prototype = Shape.prototype either, because then you can't add things to Rectangle.prototype (since it's pointing at the same object Shape.prototype is pointing at, you'd add them to Shape instances as well).

So what we want is to create a new object that uses Shape.prototype as its prototype (e.g., that inherits from Shape.prototype) just like the objects created via new Shape do, but without calling Shape to create it; and then use that new object as Rectangle.prototype.

As of ECMAScript5, we can do that using Object.create:

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

Object.create creates a new object and assigns the first argument you give it as the object's underlying prototype.

Having done that, although it's not strictly necessary, we should also set the constructor property on our new object so that it refers to Rectangle:

Rectangle.prototype.constructor = Rectangle;

We do that because that way, the relationship between Rectangle.prototype and Rectangle looks the way §13.2 of the spec says it should look. Basically, if you have a constructor function X, then X.prototype.constructor should refer to X. Sometimes people rely on that for cloning operations and such (JavaScript itself does not rely on that — for instance, instanceof doesn't rely on it).

So we can use ES5's Object.create to do this. But even today, not all JavaScript engines support Object.create. So instead, we can do this indirectly, by creating a temporary constructor function, letting it borrow Shape.prototype as its prototype property, and then using that temporary constructor to create our new object to use as Rectangle.prototype:

function Ctor() { }
Ctor.prototype = Shape.prototype; // Borrow the prototype
Rectangle.prototype = new Ctor(); // Create the new object

...and again we should set the constructor property:

Rectangle.prototype.constructor = Rectangle;

Typically, rather than writing all that out each time, you use a helper like this:

function derive(Parent, Child) {
    function Ctor() { this.constructor = Child; }
    Ctor.prototype = Parent.prototype;
    Child.prototype = new Ctor();
    return Child; // For chaining
}

Then use it like this:

derive(Shape, Rectangle);

The end result of all this is that we only call Shape from within Rectangle, to initialize instances. We don't call it to create Rectangle.prototype.

If you're interested in inheritance plumbing in JavaScript, you might be interested in my Lineage script, which handles the above and a lot more besides. When I say "interested," I don't necessarily mean using it (although you're welcome to), but looking at the unminified source and at this page comparing Lineage with doing the same thing without a helper could give you an idea how this stuff works.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you for replay! May I ask two questions: 1. The example with function Ctor() { }... Is the idea here to duplicate the prototype from Shape, but not referencing it? 2. What do you think about so called functional inheritance? Is it really better than the Pseudoclassical one (taken from the book "Java Script the good parts")? – Nikolay Nov 07 '13 at 08:17
  • I think I got it. The idea is to create empty object which references the Shape.prototype, and Shape.call() will take care for copying(or something like this) and initializing the Shape props. Is this so? – Nikolay Nov 07 '13 at 08:27
  • @Nikolay: 1. We're not *duplicating* the `Shape.prototype`, we're creating a new object that uses `Shape.prototype` as its prototype. Later, when we call `Shape`, we're **not** copying anything, we're just giving `Shape` a chance to do any per-object initialization it expects to do. – T.J. Crowder Nov 07 '13 at 08:30
  • @Nikolay: 2. All inheritance in JavaScript is prototypical. How you use and assign prototypes is a fashion choice. Beware of that part of *The Good Parts*; Crockford dislikes pseudo-classical inheritance and allows that to blind him a bit in this area. There is a statement or two about it in that section of the book that are flatly erroneous. *(cont'd)* – T.J. Crowder Nov 07 '13 at 08:34
  • *(continuing)* (Crockford -- who is a smart, well-informed man -- has a tendency to state his *opinion* as though it were fact, and his writing suffers for it. But there's a lot of good information and thinking there and on his website.) Personally, I tend toward constructor functions whenever I have a "class" of objects because that fits with how JavaScript was designed, but I very happily use direct prototypes (creating an object that directly inherits from another specific object, no builder or constructor function at all) whenever appropriate. It's hugely powerful. – T.J. Crowder Nov 07 '13 at 08:35
1

JavaScript is a prototypal object-oriented programming language. This is to say that objects inherit from other objects.

Objects in JavaScript have a special [[proto]] property which points to the prototype of that object. This property is internally maintained by the JavaScript engine and each object has a linked chain of prototypes. For example:

          null
           ^
           | [[proto]]
           |
  +------------------+
  | Object.prototype |
  +------------------+
           ^
           | [[proto]]
           |
  +------------------+
  |  Shape.prototype |
  +------------------+
           ^
           | [[proto]]
           |
+---------------------+
| Rectangle.prototype |
+---------------------+
           ^
           | [[proto]]
           |
        +------+
        | rect |
        +------+

When you write Rectangle.prototype = new Shape(); your prototype chain is as shown above. Note that the new way to do this is Rectangle.prototype = Object.create(Shape.prototype);.

          null
           ^
           | [[proto]]
           |
  +------------------+
  | Object.prototype |
  +------------------+
           ^
           | [[proto]]
           |
+---------------------+
| Rectangle.prototype |
|  / Shape.prototype  |
+---------------------+
           ^
           | [[proto]]
           |
        +------+
        | rect |
        +------+

If you write Rectangle.prototype = Shape.prototype; then your prototype chain would be as shown above. Since Rectangle.prototype is the same as Shape.prototype you'll only inherit from one prototype instead of two. This means that you can't add Rectangle specific methods to Rectangle.prototype.

Which method should you choose? I prefer using the first method as we have two separate prototypes. You can add Rectangle specific methods to Rectangle.prototype. In addition it is actually extending the prototype. In the second case there is no inheritance at all.

This is how I usually write code nowadays:

function defclass(type, body) {
    var neo = function () {
        var self = new constructor;
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    };

    var constructor = function () {}; constructor.prototype = type;
    var prototype = constructor.prototype = new constructor; prototype.new = neo;
    return body.call(prototype, type), prototype;
}

Using defclass you can now create classes as follows:

var shape = defclass(null, function () {
    this.constructor = function () {
        this.x = 0;
        this.y = 0;
    };

    this.move = function (x, y) {
        this.x += x;
        this.y += y;
        console.info("Shape moved.");
    };
});

var rectangle = defclass(shape, function (shape) {
    this.constructor = function () {
        shape.constructor.call(this);
    };
});

Finally you can create instances of classes as follows:

var rect = rectangle.new();

rect.move(2, 1);

See the demo for yourself: http://jsfiddle.net/VHfBd/1

Links:

  1. Object Inheritance in JavaScript
  2. How to achieve pseudo-classical inheritance right on the class declaration?
  3. Why Prototypal Inheritance Matters
Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299