2

I have not been able to find an answer to this. Objects in JavaScript have an inheritance chain; the chain of any function is Function => Object, the chain of an instance of TypeError is TypeError => Error => Object, and the chain of TypeError is, oddly, Function => Function => Object.

I had looked up how to make a constructed object inherit properties from another function in addition to its direct constructor, expecting the resulting inheritance chain to be object => constructor => second function and for this to be how one constructor would extend another. The solution that I had found was to call <second function>.<call or apply>(this[, optional arguments...]) inside the body of the constructor, but object instanceof <second function> ended up returning false.

Further research revealed mostly answers that use class syntax or Object.create, but those are new and one "class" extending another in JavaScript has been around since the creation of the language, so there's some other way that's used to do this. This information is something that should be mentioned right alongside basic explanations of JavaScript constructors yet it is not. What is the primary method of extending a "class" (not actual class syntax) resulting in deeper inheritance chains?

Example result:

// Square is the subclass
// Rectangle is the superclass

var rectangle = new Rectangle(1, 1);
var square = new Square(1);

rectangle instanceof Rectangle; // true
rectangle instanceof Square; // false
square instanceof Rectangle; // true
square instanceof Square; // true
Square instanceof Rectangle; // true

False solution:

function F () {
    this.value = 0;
}

function G () {
    F.apply(this);
}

var f = new F();
var g = new G();

// g gets the same properties from F that f gets.
"value" in f; // true
"value" in g; // true

// But neither g nor G are instances of F.
g instanceof G; // true
g instanceof F; // false
G instanceof F; // false
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Melab
  • 2,594
  • 7
  • 30
  • 51
  • "*The solution that I had found*" - can you share what exactly you found and where you found it? Notice that the `Parent.call(this, ...arguments)` is just the equivalent to a `super()` call, not to the `extends` clause. – Bergi Aug 10 '21 at 14:41
  • 1
    `Object.create` isn't new. It's been around since ES5, way longer than `class` syntax. – Bergi Aug 10 '21 at 14:42
  • 2
    Prior to `Object.create`, [`new` was used to create the prototype object](https://stackoverflow.com/q/12592913/1048572), but [it's never been an exact replacement](https://stackoverflow.com/q/17392857/1048572) and the proper way of doing it was complicated. Also, using complex class hierarchies never was popular. – Bergi Aug 10 '21 at 14:47
  • 1
    "*the chain of an instance of TypeError is TypeError => Error => Object*" - not exactly. I'm confused how your post never mentions `.prototype` objects anywhere. Properties are not inherited from functions. Do you understand how the prototype chain of a class instance works, and what the `new` keyword has to do with it? – Bergi Aug 10 '21 at 14:50
  • @Bergi ES5 was the first departure from "legacy" JavaScript. I added the failed solution. – Melab Aug 10 '21 at 14:54
  • @Bergi I understand how they inherit from prototypes. I used "function" instead of "class" because I didn't want to sound like I was referring to class syntax. – Melab Aug 10 '21 at 14:56
  • The short answer is that prior to Class Syntax, there were no classes, just objects and functions. Class-like extension behavior was done via Prototypes and/or constructor functions. – RBarryYoung Aug 10 '21 at 14:56
  • But they don't inherit from the class (constructor) object either. You know that a class consists of two objects? The prototype chain of the `TypeError` function is *not* Function => Function => Object. – Bergi Aug 10 '21 at 14:57
  • @RBarryYoung And *how* was that done? That's what I want to know. – Melab Aug 10 '21 at 14:57
  • @Melab "*I added the failed solution.*" - yes, that's the incomplete solution. My question was where did you find this? – Bergi Aug 10 '21 at 14:58
  • @Melab I am not a JS expert by any means, but a couple of years ago I had to learn it for a project and wrote a ton of code that was (mostly) ES4 compliant. I'd be happy to share examples but there's a ton of code and I am honestly am not sure exactly what you are looking for. "Extensions" is a little fuzzy to me and your rectangle-square inheritance example is confusing as that's the classic example of when you should *not* use class inheritance (regardless of language). If you could give a bit more detail on what specifically you are looking for, I can try to find it. – RBarryYoung Aug 10 '21 at 15:05
  • 1
    @RBarryYoung When `A` is an object/function and both `B` and `C` are different constructors, `A instanceof B`, `A instanceof C`, and `B instanceof C` can all return true. I assume that this was possible in pre-ES5. I'm saying that `B` extends `C` because I don't know what else to call it. So, how was that achieved? – Melab Aug 17 '21 at 17:41
  • @Melab I think that it's just `B.prototype = New C();` and `A.prototype = New B();` See here: http://phrogz.net/JS/Classes/OOPinJS2.html – RBarryYoung Aug 17 '21 at 19:03

2 Answers2

3

one "class" extending another in JavaScript has been around since the creation of the language

No, it hasn't. JavaScript was never (and still is not) a class-based language. The only tools you had were .prototype and new.

How were "classes" extended prior to Object.create?

Using the same approach, basically. The key to setup the prototype chain is

Subclass.prototype = Object.create(Superclass.prototype);

and without Object.create, people just created that object using

Subclass.prototype = new Superclass;

See the answers from 2010 in How to inherit from a class in javascript? for examples.
Yes, this is a bad idea, but it proliferated. Better solutions that would not execute the superclass constructor were devised, and this is how Object.create came into existence, popularised by Douglas Crockford (see also What is happening in Crockford's object creation technique?).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • And do I remember correctly that `Subclass.prototype = new Superclass;` was followed even by `Subclass.prototype.constructor = Subclass`? – Peter Seliger Aug 10 '21 at 15:30
  • @PeterSeliger Yes, but [it was not essential](https://stackoverflow.com/q/12622137/1048572), and was (is) omitted often. – Bergi Aug 10 '21 at 15:38
  • Thanks, and not having to deal with that kind of glue code anymore is one of the many reasons I'm glad about JavaScript's `class` syntax. – Peter Seliger Aug 10 '21 at 15:41
0

So I thought that I'd include a couple of examples of "extensions" in javascript from my old project just in case that is what was being looked for.

Here's an example of "extending" all objects by modifying the valueOf function. First it copies the built-in .valueOf definition to a new function/property -> .originalvalueOf, then adds my custom .valueOf over the built-in one. I did this so that JS numbers would throw an error for things like NaN or division by zero. As you can see it is done by modifying the Object.prototype, and my new .valueOf actually calls the built-in version through .originalvalueOf.

/*  ****    Object extensions    ****    

    Check for NaN and DivZeros
    (adapted from: https://stackoverflow.com/a/20535480/109122)
*/
 Object.prototype.originalValueOf = Object.prototype.valueOf;

 Object.prototype.valueOf = function() {

     if (typeof this == 'number') {
        if (!isFinite(this)) {throw new Error('Number is NaN or not Finite! (RBY)');}
     }

     return this.originalValueOf();
 }

// var a = 1 + 2; // -> works
// console.log(a); // -> 3

// var b = {};
// var c = b + 2; // -> will throw an Error

Here's an example of a constructor for my "class" called Pe2dSpatialState. Note that I've added all of the object's fields here:

// Constructor for Dynamic Spatial State
//( physical properties that change according to Newtonian Mechanics )
var Pe2dSpatialState = function(point2dPosition, 
                                point2dVelocity, 
                                point2dAcceleration, 
                                interval,
                                rotationDegrees,
                                rotationDegreesPerSec) {
    this.position = point2dPosition;            // position of this state
    this.velocity = point2dVelocity;            // velocity of this state
    this.acceleration = point2dAcceleration;    // acceleration to be applied
    
    this.interval = interval;                   // time to the next state

    this.rotationD = (rotationDegrees ? rotationDegrees : 0);
                                                // degrees rotated (because SVG uses degrees)
    this.rotationDPerSec = (rotationDegreesPerSec ? rotationDegreesPerSec : 0);
                                                // degrees per sec (because SVG uses degrees)
}

I added functions to Pe2dSpatialState objects through the prototype:

Pe2dSpatialState.prototype.clone = function() {
    var tmp = new Pe2dSpatialState( this.position.clone(),
                                this.velocity.clone(),
                                (this.acceleration ? this.acceleration.clone() : undefined),
                                this.interval,
                                this.rotationD,
                                this.rotationDPerSec);
    return tmp;
}

Then I added get/set type "properties" using Object.defineProperty:

Object.defineProperty(Pe2dSpatialState.prototype, "rotationR", {
    get()  {return this.rotationD * Math.PI * 2 / 360;},
    set(v) {this.rotationD = v * 360 / (Math.PI * 2);}
});
Object.defineProperty(Pe2dSpatialState.prototype, "rotations", {
    get()  {return this.rotationD / 360;},
    set(v) {this.rotationD = v * 360;}
});

Checking my listings, I always defined my "classes" in this order: Constructor function with fields, then adding functions/methods to the prototype and finally adding properties with Object.defineProperty. And only after that would I use it anywhere.

I cannot remember exactly why I did everything in these three different ways, except that I went through a lot of different attempts and iterations and this is what I finally landed on as working for me. (This is all probably much easier under ES6).

I also found a very sophisticated function that would list out the object-function-prototype trees for any object that was hugely helpful to me in figuring out what was really happening and what would work. I haven't included it because it's long, but if you want to see it then I will post it here.

(I cannot guarantee that this is the best way to do this nor even that there aren't any mistakes in this code. In fact, looking at it now, I suspect that some of my fields should have been properties instead...)

RBarryYoung
  • 55,398
  • 14
  • 96
  • 137