How do I create an abstract base class in JavaScript?
Asked Answered
B

18

170

Is it possible to simulate abstract base class in JavaScript? What is the most elegant way to do it?

Say, I want to do something like:

var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();

It should output:

meow
bark
Benediction answered 28/2, 2009 at 9:6 Comment(1)
S
224

JavaScript Classes and Inheritance (ES6)

According to ES6, you can use JavaScript classes and inheritance to accomplish what you need.

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance.

Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

First of all, we define our abstract class. This class can't be instantiated, but can be extended. We can also define functions that must be implemented in all classes that extends this one.

/**
 * Abstract Class Animal.
 *
 * @class Animal
 */
class Animal {

  constructor() {
    if (this.constructor == Animal) {
      throw new Error("Abstract classes can't be instantiated.");
    }
  }

  say() {
    throw new Error("Method 'say()' must be implemented.");
  }

  eat() {
    console.log("eating");
  }
}

After that, we can create our concrete Classes. These classes will inherit all functions and behaviour from abstract class.

/**
 * Dog.
 *
 * @class Dog
 * @extends {Animal}
 */
class Dog extends Animal {
  say() {
    console.log("bark");
  }
}

/**
 * Cat.
 *
 * @class Cat
 * @extends {Animal}
 */
class Cat extends Animal {
  say() {
    console.log("meow");
  }
}

/**
 * Horse.
 *
 * @class Horse
 * @extends {Animal}
 */
class Horse extends Animal {}

And the results...

// RESULTS

new Dog().eat(); // eating
new Cat().eat(); // eating
new Horse().eat(); // eating

new Dog().say(); // bark
new Cat().say(); // meow
new Horse().say(); // Error: Method say() must be implemented.

new Animal(); // Error: Abstract classes can't be instantiated.
Sushi answered 24/1, 2018 at 17:10 Comment(3)
Note the parent method eat could use console.log(this.constructor.name + " eating") to output "Cat eating", "Dog eating" or "Horse eating"Demosthenes
Note : If constructors needs to implemented in child classes, constructor implementation in child classes require super() to be called. If super() is throwing an error , child classes cannot be constructed.Bolshevik
@VishalaRamasamy Super class constructor will not throw an error because we are instantiating child classes (Cat, Dog and Horse) and not the super class (Animal).Sushi
B
155

One simple way to create an abstract class is this:

/**
 @constructor
 @abstract
 */
var Animal = function() {
    if (this.constructor === Animal) {
      throw new Error("Can't instantiate abstract class!");
    }
    // Animal initialization...
};

/**
 @abstract
 */
Animal.prototype.say = function() {
    throw new Error("Abstract method!");
}

The Animal "class" and the say method are abstract.

Creating an instance would throw an error:

new Animal(); // throws

This is how you "inherit" from it:

var Cat = function() {
    Animal.apply(this, arguments);
    // Cat initialization...
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say = function() {
    console.log('meow');
}

Dog looks just like it.

And this is how your scenario plays out:

var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();

Fiddle here (look at the console output).

Bekelja answered 19/1, 2014 at 18:40 Comment(9)
can u please explain line by line, if you dont mind, as i am new to OOPs. Thanks!Lough
@undefined: to understand it I suggest you look up prototypical inheritance in Javascript: this is a good guide.Pecos
The most important piece of this is that in the very first code snippet, there is an error being thrown. You might also throw a warning or return a null value instead of an object so as to continue application execution, it really depends on your implementation. This, in my opinion, is the correct way to implement abstract JS "classes".Tillich
@Jordão, please may I know why you have used Animal.apply(this, arguments) in the constructor of Cat object?Garrison
@G1P: that's the usual way to execute a "super-class constructor" in Javascript, and it needs to be done like that, manually.Pecos
what about abstract properties? What is the best way to define one?Rechabite
@AreTor: you could define abstract getter and setter methods for that.Pecos
@mpen See this question about abstract ES6 classesAbound
Why is a function expression used instead of a function declaration when creating the class constructor? Moreover, why "var name = function() {}", instead of, "function name(){}"?Eggleston
R
28

Do you mean something like this:

function Animal() {
  //Initialization for all Animals
}

//Function and properties shared by all instances of Animal
Animal.prototype.init=function(name){
  this.name=name;
}
Animal.prototype.say=function(){
    alert(this.name + " who is a " + this.type + " says " + this.whattosay);
}
Animal.prototype.type="unknown";

function Cat(name) {
    this.init(name);

    //Make a cat somewhat unique
    var s="";
    for (var i=Math.ceil(Math.random()*7); i>=0; --i) s+="e";
    this.whattosay="Me" + s +"ow";
}
//Function and properties shared by all instances of Cat    
Cat.prototype=new Animal();
Cat.prototype.type="cat";
Cat.prototype.whattosay="meow";


function Dog() {
    //Call init with same arguments as Dog was called with
    this.init.apply(this,arguments);
}

Dog.prototype=new Animal();
Dog.prototype.type="Dog";
Dog.prototype.whattosay="bark";
//Override say.
Dog.prototype.say = function() {
        this.openMouth();
        //Call the original with the exact same arguments
        Animal.prototype.say.apply(this,arguments);
        //or with other arguments
        //Animal.prototype.say.call(this,"some","other","arguments");
        this.closeMouth();
}

Dog.prototype.openMouth=function() {
   //Code
}
Dog.prototype.closeMouth=function() {
   //Code
}

var dog = new Dog("Fido");
var cat1 = new Cat("Dash");
var cat2 = new Cat("Dot");


dog.say(); // Fido the Dog says bark
cat1.say(); //Dash the Cat says M[e]+ow
cat2.say(); //Dot the Cat says M[e]+ow


alert(cat instanceof Cat) // True
alert(cat instanceof Dog) // False
alert(cat instanceof Animal) // True
Reception answered 28/2, 2009 at 12:50 Comment(2)
Perhaps I missed it. Where is the base class (Animal) abstract?Gobelin
@Gobelin Yes, you missed that this was answered about five years ago, that javascript at that time did not have abstract classes, that the question was for a way to simulate it (whattosay is not defined in animal), and that this answer clearly asks if the proposed answer was what the questioner was looking for. It does not claim to give a solution for abstract classes in javascript. The questioner didn't bother to reply to me or to anyone else so i have no idea if it worked for him. I'm sorry if a five year old proposed answer to someone elses question didn't work out for you.Reception
P
15

You might want to check out Dean Edwards' Base Class: http://dean.edwards.name/weblog/2006/03/base/

Alternatively, there is this example / article by Douglas Crockford on classical inheritance in JavaScript: http://www.crockford.com/javascript/inheritance.html

Password answered 2/3, 2009 at 13:7 Comment(1)
Regarding the Crockford link, it's a load of rubbish. He's added a note to the end of that article: "I now see my early attempts to support the classical model in JavaScript as a mistake."Resolved
A
11

Is it possible to simulate abstract base class in JavaScript?

Certainly. There are about a thousand ways to implement class/instance systems in JavaScript. Here is one:

// Classes magic. Define a new class with var C= Object.subclass(isabstract),
// add class members to C.prototype,
// provide optional C.prototype._init() method to initialise from constructor args,
// call base class methods using Base.prototype.call(this, ...).
//
Function.prototype.subclass= function(isabstract) {
    if (isabstract) {
        var c= new Function(
            'if (arguments[0]!==Function.prototype.subclass.FLAG) throw(\'Abstract class may not be constructed\'); '
        );
    } else {
        var c= new Function(
            'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
            'if (arguments[0]!==Function.prototype.subclass.FLAG && this._init) this._init.apply(this, arguments); '
        );
    }
    if (this!==Object)
        c.prototype= new this(Function.prototype.subclass.FLAG);
    return c;
}
Function.prototype.subclass.FLAG= new Object();

var cat = new Animal('cat');

That's not really an abstract base class of course. Do you mean something like:

var Animal= Object.subclass(true); // is abstract
Animal.prototype.say= function() {
    window.alert(this._noise);
};

// concrete classes
var Cat= Animal.subclass();
Cat.prototype._noise= 'meow';
var Dog= Animal.subclass();
Dog.prototype._noise= 'bark';

// usage
var mycat= new Cat();
mycat.say(); // meow!
var mygiraffe= new Animal(); // error!
Axiom answered 28/2, 2009 at 11:12 Comment(2)
Why do use the evil new Function(...) construct? Wouldn't var c = function () { ... }; be better?Fireboard
“var c= function() {...}” would create a closure over anything in subclass() or other containing scope. Probably not important, but I wanted to keep it clean of potentially-unwanted parent scopes; the pure-text Function() constructor avoids closures.Axiom
K
11
Animal = function () { throw "abstract class!" }
Animal.prototype.name = "This animal";
Animal.prototype.sound = "...";
Animal.prototype.say = function() {
    console.log( this.name + " says: " + this.sound );
}

Cat = function () {
    this.name = "Cat";
    this.sound = "meow";
}

Dog = function() {
    this.name = "Dog";
    this.sound  = "woof";
}

Cat.prototype = Object.create(Animal.prototype);
Dog.prototype = Object.create(Animal.prototype);

new Cat().say();    //Cat says: meow
new Dog().say();    //Dog says: woof 
new Animal().say(); //Uncaught abstract class! 
Karaite answered 18/9, 2013 at 21:4 Comment(1)
can a subclass's constructor invoke the superclass's constructor? (like calling super in some language... if so, then it will unconditionally raise an exceptionUltrared
D
6

Another thing you might want to enforce is making sure your abstract class is not instantiated. You can do that by defining a function that acts as FLAG ones set as the Abstract class constructor. This will then try to construct the FLAG which will call its constructor containing exception to be thrown. Example below:

(function(){

    var FLAG_ABSTRACT = function(__class){

        throw "Error: Trying to instantiate an abstract class:"+__class
    }

    var Class = function (){

        Class.prototype.constructor = new FLAG_ABSTRACT("Class");       
    }

    //will throw exception
    var  foo = new Class();

})()
Dependence answered 7/3, 2012 at 21:8 Comment(0)
E
6

Question is quite old, but I created some possible solution how to create abstract "class" and block creation of object that type.

//our Abstract class
var Animal=function(){
  
    this.name="Animal";
    this.fullname=this.name;
    
    //check if we have abstract paramater in prototype
    if (Object.getPrototypeOf(this).hasOwnProperty("abstract")){
    
    throw new Error("Can't instantiate abstract class!");
    
    
    }
    

};

//very important - Animal prototype has property abstract
Animal.prototype.abstract=true;

Animal.prototype.hello=function(){

   console.log("Hello from "+this.name);
};

Animal.prototype.fullHello=function(){

   console.log("Hello from "+this.fullname);
};

//first inheritans
var Cat=function(){

	  Animal.call(this);//run constructor of animal
    
    this.name="Cat";
    
    this.fullname=this.fullname+" - "+this.name;

};

Cat.prototype=Object.create(Animal.prototype);

//second inheritans
var Tiger=function(){

    Cat.call(this);//run constructor of animal
    
    this.name="Tiger";
    
    this.fullname=this.fullname+" - "+this.name;
    
};

Tiger.prototype=Object.create(Cat.prototype);

//cat can be used
console.log("WE CREATE CAT:");
var cat=new Cat();
cat.hello();
cat.fullHello();

//tiger can be used

console.log("WE CREATE TIGER:");
var tiger=new Tiger();
tiger.hello();
tiger.fullHello();


console.log("WE CREATE ANIMAL ( IT IS ABSTRACT ):");
//animal is abstract, cannot be used - see error in console
var animal=new Animal();
animal=animal.fullHello();

As You can see last object give us error, it is because Animal in prototype has property abstract. To be sure it is Animal not something which has Animal.prototype in prototype chain I do:

Object.getPrototypeOf(this).hasOwnProperty("abstract")

So I check that my closest prototype object has abstract property, only object created directly from Animal prototype will have this condition on true. Function hasOwnProperty checks only properties of current object not his prototypes, so this gives us 100% sure that property is declared here not in prototype chain.

Every object descended from Object inherits the hasOwnProperty method. This method can be used to determine whether an object has the specified property as a direct property of that object; unlike the in operator, this method does not check down the object's prototype chain. More about it:

In my proposition we not have to change constructor every time after Object.create like it is in current best answer by @Jordão.

Solution also enables to create many abstract classes in hierarchy, we need only to create abstract property in prototype.

Extraterritorial answered 19/9, 2016 at 10:25 Comment(0)
S
5
function Animal(type) {
    if (type == "cat") {
        this.__proto__ = Cat.prototype;
    } else if (type == "dog") {
        this.__proto__ = Dog.prototype;
    } else if (type == "fish") {
        this.__proto__ = Fish.prototype;
    }
}
Animal.prototype.say = function() {
    alert("This animal can't speak!");
}

function Cat() {
    // init cat
}
Cat.prototype = new Animal();
Cat.prototype.say = function() {
    alert("Meow!");
}

function Dog() {
    // init dog
}
Dog.prototype = new Animal();
Dog.prototype.say = function() {
    alert("Bark!");
}

function Fish() {
    // init fish
}
Fish.prototype = new Animal();

var newAnimal = new Animal("dog");
newAnimal.say();

This isn't guaranteed to work as __proto__ isn't a standard variable, but it works at least in Firefox and Safari.

If you don't understand how it works, read about the prototype chain.

Sauer answered 28/2, 2009 at 9:58 Comment(6)
proto work AFAIK only in FF and Chome (neither IE nor Opera supports it. I haven't tested in Safari). BTW, you are doing it wrong: the base class (animal) should be edited every time a new type of animal is wanted.Reception
Safari and Chrome both use the same javascript engine. I wasn't really sure he only wanted to know how inheritance works, therefore I tried to follow his example as closely as possible.Sash
Safari and Chrome don't use the same JavaScript engine, Safari uses JavaScriptCore and Chrome uses V8. The thing that both browsers share is the Layout Engine, WebKit.Fratricide
@GeorgSchölly Please consider editing your answer to use the new Object.getPrototypeOf constructChinoiserie
@Benjamin: According to Mozilla there is no setPrototypeOf method yet which I would need for my code.Sash
@GeorgSchölly Hi :) First of all, when you create an object using a function constructor it sets its prototype, when you do a Dog.prototype=new Animal() you're setting the prototype of Dog objects :) . Second, there is a setPrototypeOf, it's called Object.create, but that's another story .Chinoiserie
T
5

You can create abstract classes by using object prototypes, a simple example can be as follows :

var SampleInterface = {
   addItem : function(item){}  
}

You can change above method or not, it is up to you when you implement it. For a detailed observation, you may want to visit here.

Thuja answered 22/5, 2013 at 17:44 Comment(0)
B
3

Javascript can have inheritance, check out the URL below:

http://www.webreference.com/js/column79/

Andrew

Brolly answered 28/2, 2009 at 9:13 Comment(1)
Other possibilities are javascript.crockford.com/inheritance.html and javascript.crockford.com/prototypal.htmlFireboard
B
2

We can use Factory design pattern in this case. Javascript use prototype to inherit the parent's members.

Define the parent class constructor.

var Animal = function() {
  this.type = 'animal';
  return this;
}
Animal.prototype.tired = function() {
  console.log('sleeping: zzzZZZ ~');
}

And then create children class.

// These are the child classes
Animal.cat = function() {
  this.type = 'cat';
  this.says = function() {
    console.log('says: meow');
  }
}

Then define the children class constructor.

// Define the child class constructor -- Factory Design Pattern.
Animal.born = function(type) {
  // Inherit all members and methods from parent class,
  // and also keep its own members.
  Animal[type].prototype = new Animal();
  // Square bracket notation can deal with variable object.
  creature = new Animal[type]();
  return creature;
}

Test it.

var timmy = Animal.born('cat');
console.log(timmy.type) // cat
timmy.says(); // meow
timmy.tired(); // zzzZZZ~

Here's the Codepen link for the full example coding.

Barrera answered 5/4, 2017 at 14:3 Comment(0)
B
2

Using ES6 Classes and new.target

class Animal {
  constructor() {
    if(new.target===Animal)
    throw new Error('Cannot be instantiated')
  }
  //Non-abstract method
  makeSound()
  {
     console.log(this.sound);
  }
}

class Cat extends Animal {
  constructor(sound) {
    super();
    this.sound = sound;
  }
}

class Dog extends Animal {
  constructor(sound) {
    super();
    this.sound = sound;
  }
}

let cat1 = new Cat('Meow')
cat1.makeSound();
let dog1 = new Dog('Bark')
dog1.makeSound();
let genericAnimal = new Animal(); //Throws Error

//ES6 - Abstract class with Abstract and Non-Abstract methods
class Animal {
  constructor() {
    if(new.target===Animal)
    throw new Error('Abstract Class cannot be instantiated')
  }
  //abstract method
    makeSound()
  {
     throw new Error('Abstract Method cannot be called')
  }
  //non-abstract method
  displayType()
  {
     console.log(this.name," instanceof Animal",this instanceof Animal);
  }
}

class Cat extends Animal {
  constructor(name,sound) {
    super();
    this.name = name;
    this.sound = sound;
  }
  //abstract method defined in child class implementation
  makeSound()
  {
     console.log("Cat ",this.name, " is making ", this.sound);
  }
}

class Dog extends Animal {
  constructor(name,sound) {
    super();
    this.name = name;
    this.sound = sound;
  }
}
//Dog.prototype.constructor = Dog;

let cat1 = new Cat('Bella','Meow')
cat1.makeSound();
cat1.displayType();
let dog1 = new Dog('Jimmy','Bark')
dog1.displayType();
dog1.makeSound(); //throws error
//let genericAnimal = new Animal(); //throws error
Bolshevik answered 24/8, 2023 at 18:17 Comment(0)
T
1
//Your Abstract class Animal
function Animal(type) {
    this.say = type.say;
}

function catClass() {
    this.say = function () {
        console.log("I am a cat!")
    }
}
function dogClass() {
    this.say = function () {
        console.log("I am a dog!")
    }
}
var cat = new Animal(new catClass());
var dog = new Animal(new dogClass());

cat.say(); //I am a cat!
dog.say(); //I am a dog!
Triste answered 8/11, 2017 at 1:10 Comment(1)
This is polymorphism in JavaScript, you can do some check to implement override.Triste
J
0

I think All Those answers specially first two (by some and jordão) answer the question clearly with conventional prototype base JavaScript concept.

Now as you want the animal class constructor to behave according to the passed parameter to the construction, I think this is very much similar to basic behavior of Creational Patterns for example Factory Pattern.

Here i made a little approach to make it work that way.

var Animal = function(type) {
    this.type=type;
    if(type=='dog')
    {
        return new Dog();
    }
    else if(type=="cat")
    {
        return new Cat();
    }
};

Animal.prototype.whoAreYou=function()
{
    console.log("I am a "+this.type);
}

Animal.prototype.say = function(){
    console.log("Not implemented");
};

var Cat =function () {
    Animal.call(this);
    this.type="cat";
};

Cat.prototype=Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.say=function()
{
    console.log("meow");
}

var Dog =function () {
    Animal.call(this);
    this.type="dog";
};

Dog.prototype=Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.say=function()
{
    console.log("bark");
}

var animal=new Animal();

var dog = new Animal('dog');
var cat=new Animal('cat');

animal.whoAreYou(); //I am a undefined
animal.say(); //Not implemented

dog.whoAreYou(); //I am a dog
dog.say(); //bark

cat.whoAreYou(); //I am a cat
cat.say(); //meow
Jade answered 1/2, 2015 at 6:4 Comment(1)
Linking to this: programmers.stackexchange.com/questions/219543/… This Animal constructor can be seen as an anti-pattern, a superclass shouldn't have knowledge about subclasses. (violates Liskov and Open/Close principle)Psychochemical
H
0
/****************************************/
/* version 1                            */
/****************************************/

var Animal = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};
var Cat = function() {
    Animal.call(this, "moes");
};

var Dog = function() {
    Animal.call(this, "vewa");
};


var cat = new Cat();
var dog = new Dog();

cat.say();
dog.say();


/****************************************/
/* version 2                            */
/****************************************/

var Cat = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Dog = function(params) {
    this.say = function()
    {
        console.log(params);
    }
};

var Animal = function(type) {
    var obj;

    var factory = function()
    {
        switch(type)
        {
            case "cat":
                obj = new Cat("bark");
                break;
            case "dog":
                obj = new Dog("meow");
                break;
        }
    }

    var init = function()
    {
        factory();
        return obj;
    }

    return init();
};


var cat = new Animal('cat');
var dog = new Animal('dog');

cat.say();
dog.say();
Helton answered 18/3, 2016 at 23:9 Comment(3)
In my point of view, this is a most elegant way to have good results with less code.Helton
Please explain why this is useful. Handing out code isn't nearly as useful as explaining why the code is useful. It's the difference between handing someone a fish, or teaching them how to fish.Metonymy
Many do not use in their javascript programming techniques prototype or constructors. Even if they are useful in many situations. For those, i consider that code is useful. Not because code it is better than others .. but because it is easier to understandHelton
E
0

If you want to make sure that your base classes and their members are strictly abstract here is a base class that does this for you:

class AbstractBase{
    constructor(){}
    checkConstructor(c){
        if(this.constructor!=c) return;
        throw new Error(`Abstract class ${this.constructor.name} cannot be instantiated`);
    }
    throwAbstract(){
        throw new Error(`${this.constructor.name} must implement abstract member`);}    
}

class FooBase extends AbstractBase{
    constructor(){
        super();
        this.checkConstructor(FooBase)}
    doStuff(){this.throwAbstract();}
    doOtherStuff(){this.throwAbstract();}
}

class FooBar extends FooBase{
    constructor(){
        super();}
    doOtherStuff(){/*some code here*/;}
}

var fooBase = new FooBase(); //<- Error: Abstract class FooBase cannot be instantiated
var fooBar = new FooBar(); //<- OK
fooBar.doStuff(); //<- Error: FooBar must implement abstract member
fooBar.doOtherStuff(); //<- OK

Strict mode makes it impossible to log the caller in the throwAbstract method but the error should occur in a debug environment that would show the stack trace.

Eyrie answered 23/2, 2018 at 12:9 Comment(0)
A
0

"use strict";



function Abstract (...arg){
    
    // create abstract constructor
    if(  this.constructor.name === 'Object' || this.constructor === Abstract ) throw { ErrorType : "can't call abstract class with new !" , }
 

    // ceate abstract method
    Object.defineProperty( this , 'config' , {
        value : function(){
            console.log('config parent')
        }
    });


    // or other 
    return this ;
};
 

class Home extends Abstract{
    name = '';
    constructor(...arg){
        super(...arg) ; 
    }

    config(){
        // this method not working
        console.log('config child')
    }
}

let y = new Home( "home" , 'dasd');
y.config();
Allopath answered 8/2, 2022 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.