Skip to main content
Silly-V
Legend
July 26, 2021
Question

ES3 prototypal inheritance

  • July 26, 2021
  • 1 reply
  • 1477 views

After many years I decided to see if I can take advantage of javascript inheritance that is properly available in the modern js versions through the class & extends keywords.

My standard operating procedure for errors for example is to create a new es3 class (function) "MyCustomError" and throw it from where it was detected at.

In the catch block, I check (e instanceof MyCustomError) and it tells me whether this was a standard uncaught garden-variety Error or my very special MyCustomError. This is ok really, but succeeds due to the throw/catch blocks which treat the thrown item as an "error" while preserving the actual class of it even if it isn't Error. Catch blocks like this are fine until you want to do something like catch all error instances and then dig deeper to see what kind of error it was without doing things like adding a custom property to an Error or dissecting its message for special strings.

So after looking for a while on how to make things inherit in ES3 js I came up with this and it appears to be working.

In the method down at the bottom it has a testMethod() function which should only ever return a valid result (string) or an Error. In the innerTestMethod() some possible errors of different kinds are thrown and inherit from Error. So when iterating through the results provided by testMethod we can be sure that we'll only come across either valid string results or one of the possibe Error object classes.

Checking instanceof for Error (optional here as we know that the only other kind of result is a valid string) the code will now enter an area where it can only be dealing with either Error, SpecialTypeCustomError or CustomError. Checking instanceof for the more specific items (SpecialTypeCustomError) at the top and going down to most generic (Error) lets me handle the various situations.

Now, what I learned is that in our ES3 there's no automatic intuitive inheriting as is in the modern javascript so we have to rig up through the Object.assign and prototypes such an object that javascript will think is actually in some other classes' prototype chain. However, what I have appears to be working and it's done with minimal code compared to some other "extend" polyfills and that's part of the reason why I'm not all 100% sure it's how it's done - but it works here.
This would be useful in cases where one wants to use instanceof to differentiate among objects which may be similar, but not want to do so by checking properties to determine with custom code on what the object actually is.
Check it out:

 

#target illustrator
#targetengine "TEST"

function test () {

  // ------------------------------------------ necessary polyfills
  if (typeof Object.create != 'function') {
    Object.create = (function () {
      var Temp = function () {};
      return function (prototype) {
        if (arguments.length > 1) {
          throw Error('Second argument not supported');
        }
        if(prototype !== Object(prototype) && prototype !== null) {
          throw TypeError('Argument must be an object or null');
        }
        if (prototype === null) { 
          throw Error('null [[Prototype]] not supported');
        }
        Temp.prototype = prototype;
        var result = new Temp();
        Temp.prototype = null;
        return result;
      };
    })();
  };

  if (!('assign' in Object)) {
    Object.assign = function (has) {
      'use strict';
      return assign;
      function assign (targetsource) {
        for (var i = 1i < arguments.lengthi++) {
          copy(targetarguments[i]);
        }
        return target;
      }
      function copy (targetsource) {
        for (var key in source) {
          if (has.call(sourcekey)) {
            target[key] = source[key];
          }
        }
      }
    }({}.hasOwnProperty);
  }
  // ----------------------------------------- /necessary polyfills

  function CustomError (msg) {
    this.message = null;
    Object.assign(thisnew Error(msg));
  };
  CustomError.prototype = Object.create(Error.prototype);

  function SpecialTypeCustomError (msgspecialCategory) {
    Object.assign(thisnew CustomError(msg));
    this.specialCategory = specialCategory;
  };
  SpecialTypeCustomError.prototype = Object.create(CustomError.prototype);

  function DifferentError (msgerrorType) {
    this.errorType = errorType;
    Object.assign(thisnew Error(msg));
  };
  DifferentError.prototype = Object.create(Error.prototype);

  function SpecialTypeDifferentError (msgerrorTypespecialCategory) {
    this.errorType = errorType;
    this.specialCategory = specialCategory;
    Object.assign(thisnew DifferentError(msgerrorType));
  };
  SpecialTypeDifferentError.prototype = Object.create(DifferentError.prototype);

  try {
    // throw new Error("Regular Error");
    // throw new CustomError("A custom error");
    // throw new SpecialTypeCustomError("A sub-class of a customer error", "ABC");
    // throw new DifferentError("A 'different' error.", "Type-1");
    // throw new SpecialTypeDifferentError("A <Special!> 'different' error.", "Type-SPCL", "1234");
  } catch (e) {
    var msg = "";
    msg += ("Has message: " + ("message" in e)) + "\n";
    msg += ("message: " + e.message) + "\n----------\n";
    msg += ("Has description: " + ("description" in e)) + "\n";
    msg += ("description: " + e.description) + "\n----------\n";
    msg += ("Has specialCategory: " + ("specialCategory" in e)) + "\n";
    msg += ("specialCategory: " + e.specialCategory) + "\n----------\n";
    msg += ("Has errorType: " + ("errorType" in e)) + "\n";
    msg += ("errorType: " + e.errorType) + "\n----------\n";

    var msg2 = "";
    msg2 += ("is Error: " + (e instanceof Error)) + "\n";
    msg2 += ("is CustomError: " + (e instanceof CustomError)) + "\n";
    msg2 += ("is SpecialTypeCustomError: " + (e instanceof SpecialTypeCustomError)) + "\n";
    msg2 += ("is DifferentError: " + (e instanceof DifferentError)) + "\n";
    msg2 += ("is SpecialTypeDifferentError: " + (e instanceof SpecialTypeDifferentError)) + "\n";
    
    alert(msg + "\n\n" + msg2);
  }

  /**
   * @9397041 {number} rand
   * @Returns {string}
   */
  function innerTestMethod (rand) {
    var product = 10 * rand;
    var displayProduct = product.toFixed(2);
    if (product < 3) {
      throw new CustomError("Is less than 3 (" + displayProduct + ")");
    } else if (product > 8) {
      throw new SpecialTypeCustomError("Is more than 8 (" + displayProduct + ")""8+");
    } else if (product >=4 && product <=6) {
      null.blah();
    }
    return "All clear";
  }

  /**
   * @Returns {string | Error}
   */
  function testMethod () {
    var rand = Math.random();
    var result;
    try {
      result = innerTestMethod(rand);
    } catch (e) {
      result = e;
    }
    return result;
  };
  /** @TyPe {Array<string | Error>} */
  var allResults = [];
  for (var i = 0i < 10i++) {
    allResults.push(testMethod(i));
  }

  var thisItem;
  var msg3 = "";
  var prefix;
  for (var i = 0i < allResults.lengthi++) {
    thisItem = allResults[i];
    prefix = (i + 1) + ") ";
    if (typeof(thisItem) == "string") {
      msg3 += prefix + thisItem + "\n";
    } else if (thisItem instanceof Error) {
      if (thisItem instanceof SpecialTypeCustomError) {
        msg3 += prefix + "SpecialTypeCustomError (msg: " + thisItem.message + ", cat: " + /** @TyPe {SpecialTypeCustomError} */ (thisItem).specialCategory + ")" + "\n";
      } else if (thisItem instanceof CustomError) {
        msg3 += prefix + "CustomError (desc: " + thisItem.description + ")" + "\n";
      } else {
        msg3 += prefix + "Error (unhandled): " + thisItem.message + "\n";
      }
    }   
  }
  alert(msg3);
};
test();

1 reply

m1b
Community Expert
Community Expert
July 27, 2021

Thanks for sharing this @Silly-V it looks intriguing! There is some voodoo you are doing. Studying this I think will help me understand some things I've struggled with. Thanks again.

- Mark

femkeblanco
Legend
July 27, 2021

I do not wish to hijack this thread (my apology if this is the case), but I thought this was an opportunity to share a short  summary of (my understanding of) prototypes for anyone wanting to get into the subject.

 

// polyfills
Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
};
// function extend(Child, Parent) {
//     Child.prototype = Object.create(Parent.prototype);
//     Child.prototype.constructor = Child;
// }
// my basic Object.assign polyfill
Object.assign = function (target) {
    for (var i = 1; i < arguments.length; i++) {
        for (var key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
                target[key] = arguments[i][key];
            }
        }
    }
};
// ----------------------------------------------------------------------
// part1: inheritance
// objects A & B, constructors
var A = function () {
    this.prop = "a";
};
A.prototype.modify = function () {
    return this.prop + "+";
};
var B = function () {
    this.prop = "b";
};
B.prototype = Object.create(A.prototype);  // B inherits A's prototype
B.prototype.constructor = B;
// or package into "extend" function (see polyfills above)
// extend(B, A);
// objects a & b, instances
var a = new A();
alert ( a.modify() );  // a+
var b = new B();
alert( b.modify() );  // b+
// ----------------------------------------------------------------------
// part2: composition
// pick n choose functions to add to prototype
var modification1 = {
    modify1: function () {return this.prop + "+";}
};
var modification2 = {
    modify2: function () {return this.prop + "++";}
};
var modification3 = {
    modify3: function () {return this.prop + "+++";}
};
// objects C & D, constructors
var C = function () {
    this.prop = "c";
};
Object.assign(C.prototype, modification1, modification2);
var D = function () {
    this.prop = "d";
};
Object.assign(D.prototype, modification2, modification3);
// objects c & d, instances
var c = new C();
alert( c.modify1() + ", " + c.modify2() );  // c+, c++
var d = new D();
alert( d.modify2() + ", " + d.modify3() );  // d++, d+++

 

 

m1b
Community Expert
Community Expert
July 27, 2021

Thanks @femkeblanco that's really helpful too. I don't have anything to offer myself as you are both working beyond my knowledge! Excellent!