How were "classes" extended prior to class syntax and Object.create?

132 Views Asked by At

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
2

There are 2 best solutions below

3
Bergi On

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

0
RBarryYoung On

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