Forum Discussion

zelot's avatar
zelot
New Contributor
7 years ago
Solved

javascript inheritance

hello,

I have a problem with inheritance using javascript. I have the following code:

file: MyFirst

class A{
  constructor(x){
  this.x = x;
  }
  
  static getA(){
    return this.x;
  } 
}

file: MySecond
var ms= require("MyFirst");
class B extends ms (ms.A doesn't work as well){
  static getB(){
    ms.A.getA();
  } 
}
module.exports.B = B;

file: Test
var ss = require("MySecond");

function firstTest(){
  ss.B.getB();
} 

and I get this error:

TypeError: class extends value [object Object] is not a function or null

 

  • KSQian's avatar
    KSQian
    7 years ago

    I was trying to answer a JavaScript question but accidentally committed sin. I apologize :( 

     

    For OP: Below is how I'd export and require.

     

    //file 1
    
    class Foo {
    
    }
    
    module.exports = Foo
    
    //file 2 
    const Foo = require('file1')
    class Bar extends Foo {
    
    }

     

6 Replies

  • tristaanogre's avatar
    tristaanogre
    Esteemed Contributor

    Ya know, I was just playing with class inheritance the other day working on my Balrog vs. Gandalf experiment.  Because the Balrog and Gandalf are both "beings" of sorts, they share a lot of common stuff (they can be falling, they have an elevation, they can die, etc).  so, rather than repeating properties and methods on two different classes, I wanted to create a base class that could be extended to them both.  I ran into exactly the same issues you did... and here's how I solved it.  First, the file with my being class:

     

    class being{
        constructor(){
            this.elevation = 1;
            this.isFallingWayDown = false;
            this.isSmote = false;
            this.isDead = false;
        } 
        hitBottom(){
            this.isFallingWayDown = false;
        }   
        fallWayDown(){
            this.elevation--
            if (this.elevation === 0){
                this.hitBottom();
            } 
        }  
        fall(){
            this.isFallingWayDown = true;
        } 
        die(){
            this.isDead = true;
            Log.Message('default death');
        } 
    } 
    
    module.exports = {being: new being()}

    Next, I created a classWizard that will instantiate Gandalf

     

    var classBeing = require('classBeing');
    
    
    var yellAttributes = Log.CreateNewAttributes();
        yellAttributes.BackColor = clSilver;
        yellAttributes.FontColor = clBlue;
        yellAttributes.Italic = true;  
          
    var speakAttributes = Log.CreateNewAttributes();
        speakAttributes.BackColor = clBlue;
        speakAttributes.FontColor = clSilver;
        speakAttributes.Italic = false;    
    var actionAttributes = Log.CreateNewAttributes();
        actionAttributes.BackColor = clWhite;
        actionAttributes.FontColor = clPurple;     
        
    var being = classBeing.being.constructor;        
    
    class Wizard extends being{
        constructor(){
            super();
        } 
        yell(words){
            Log.Message(words, '', pmNormal, yellAttributes);
        } 
        speak(words){
            Log.Message(words, '', pmNormal, speakAttributes);
        } 
        strikeGround(groundObject, enemy){
            Log.Message('--LIGHTNING SMASH!--', '', pmNormal, actionAttributes);      
            if (groundObject.constructor.name === 'Bridge'){
                groundObject.collapse();
                enemy.fall();
            } 
        } 
        smiteEnemy(enemyObject){
            this.speak('I threw down my enemy and smote his ruin upon the mountainside');
            enemyObject.die();
        } 
        die(){
            this.isDead = true;
            Log.Message('Darkness took him. And he strayed out of thought and time.', '', pmNormal, actionAttributes);
            aqUtils.Delay(10000, 'Stars wheeled overhead, and every day was as long as the life age of the earth.');
        } 
        resurrect(){
            this.isDead = false;
            this.speak('And I come back to you now, at the turn of the tide.');
        } 
        kickButt(){
            Log.Message('--SMASH ENEMY WITH SWORD--', '', pmNormal, actionAttributes);
        }
    } 
    
    module.exports.Wizard = Wizard;

    The key is that the "extends" points to the constructor method of an instance of the being and not the class itself.  This is how I worked around it because, in the TestComplete environment, classes and objects are "local" to the individual files so, to access them in another file, you need to return an object instance.  

     

    See if this works for you.  There may be a better way, but this is how I solved inheritance 

     

     

    • KSQian's avatar
      KSQian
      Contributor

      Javascript uses prototypical inheritance. "class" is just an es6 syntax sugar of prototypical inheritance. I would first learn about it first via some simple youtube search. In short, my suggestion is to familiarize yourself with Javascript's prototypical inheritance first before jumping into classes.

       

      Sometimes classes can be hard to implement.

       

      Like Robert's example. Sometimes it just feels weird to create a base class for the sheer reason to incorporate the classes below it.

       

      I want a wizard and I want a monster and they can both shoot lightnings. But do I really need to think of a 'base class' where shootLightning is a property? Other than shooting lightnings and maybe 1-2 other features, wizards aren't really monsters... they just happen to share a few things in common.

       

      We can take advantage of Javascript's prototypical inheritance.

       

      //First we create a 'prototype', which are basically objects. 
      
      const monsters = { 
          type : "monster"
      }
      
      const wizards = { 
         type : "wizard"
      }
      
      //Later on we can define properties. This is good because later on in the dev cycle we can come up with new properties. 
      
      const ability_lightning = function() {
         console.log("I shoot lightning! pew!") 
      }
      
      // Lets say now I want to assign my ability to both monsters and wizards.
      // I can now do:
      
      Object.assign(monsters, {lightning : ability_lightning })
      
      Object.assign(wizards, {lightning : ability_lightning })
      
      // I can create instances 
      const godzilla = Object.create(monsters)
      
      godzilla.lightning()
      //I shoot ligntning!
      
      const gandalf = Object.create(wizards)
      console.log(gandalf.type) //wizard 

       

       EDIT:

       

      As for your error, I believe it is because you didn't export in your first file.

       

      • tristaanogre's avatar
        tristaanogre
        Esteemed Contributor

        Great stuff, KSQian... although, a negative Kudo for mixing genre...  Fantasy wizards and kaiju don't mix. ;)

         

        I typically use prototype classing for my JavaScript work in TestComplete but my Gandalf/Balrog exercise was simply to play around with the class syntax.  There's more than one way to skin the orc... ;)

         

         

    • llgg716's avatar
      llgg716
      Occasional Contributor

      tristaanogre  solution works for me!

      Despite the wired format and syntax just for TestComplete IDE, it works in the end. :smileyvery-happy: