42

I have a class

function Node() {
    //implementation
}

and another class

function AttributionalNode() {
    this.prototype.setAttr = function (attr) {
        this.atText = attr;
    };
}

AttributionalNode.prototype = new Node();
AttributionalNode.prototype.constructor = AttributionalNode;

How to make class Node() so it can't be instantiated? e.g when I try

var node = new Node();

So it throws an Exception?

Domenic
  • 110,262
  • 41
  • 219
  • 271
Alexandr Sargsyan
  • 656
  • 1
  • 6
  • 21
  • Put `throw new Exception()` in the constructor, maybe? I dunno if that's valid in Javascript. – Nic May 31 '15 at 15:55
  • Generally it's not a good idea to set prototype properties in the constructor function. – Pointy May 31 '15 at 15:56

5 Answers5

57

In JavaScript engines that support ECMAScript 2015 (aka ES6) class syntax, this can be accomplished using the new.target meta-property:

function Node() {
   if (new.target === Node) throw TypeError("new of abstract class Node");
}

or using class syntax:

class Node {
   constructor () {
      if (new.target === Node) throw TypeError("new of abstract class Node");
   }
}

in either case, just define AttributionalNode as:

class AttributionalNode extends Node {
   constructor () {
      super();
   }
   setAttr(attr) {
      this.atText = attr;
   }
}

new Node();               // will throw TypeError
new AttributionalNode();  // works fine

For a more detailed explanation of new.target see section 4.2 of this document.

Quentin Hayot
  • 7,786
  • 6
  • 45
  • 62
Allen Wirfs-Brock
  • 1,094
  • 1
  • 8
  • 10
17

This would work:

function Node() {
    if (this.constructor === Node) {
        throw new Error("Cannot instantiate this class");
    }
}

function AttributionalNode() {
    Node.call(this); // call super
}

AttributionalNode.prototype = Object.create(Node.prototype);
AttributionalNode.prototype.setAttr = function (attr) {
    this.atText = attr;
};
AttributionalNode.prototype.constructor = AttributionalNode;

var attrNode = new AttributionalNode();
console.log(attrNode);
new Node();

Note: you cannot refer to this.prototype inside the constructor, as the prototype is only a property of the constructor function, not of the instances.

Also, see here for a good article on how to properly extend JS classes.

levi
  • 23,693
  • 18
  • 59
  • 73
8

Adapting @levi's answer, you can go with a similar solution for using with ES6 today (as new.target isn't established yet):

You can see it running on Babel's repl: http://bit.ly/1cxYGOP

class Node {
    constructor () {
      if (this.constructor === Node) 
          throw new Error("Cannot instantiate Base Class");
    }

    callMeBaby () {
      console.log("Hello Baby!");
    }
}

class AttributionalNode extends Node {
  constructor () {
    super();
    console.log("AttributionalNode instantiated!");
  }
}

let attrNode = new AttributionalNode();
attrNode.callMeBaby();

let node = new Node();
Ciro Costa
  • 2,455
  • 22
  • 25
4

Although the question has a javascript tag, because nowadays a lot of projects are using typescript on top of JS, it's worth noting that TS has support for abstract classes and methods out of the box

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log("roaming the earth...");
    }
}
Bogdan D
  • 5,321
  • 2
  • 31
  • 32
1

Based on these comments, I wrote this

class AbstractClass {
    constructor() {
        if(new.target === AbstractClass || this.__proto__.__proto__.constructor === AbstractClass)
            throw new TypeError("Cannot construct "+ this.constructor.name + " class instances directly");
        let exceptions = {};
        let currProto = this;
        while(currProto.constructor !== AbstractClass ) {
            for(let method of (currProto.constructor.abstractMethods || [])) {
                if("function" !== typeof(this[method]))
                    exceptions[method] = currProto.constructor.name;
            }
            currProto = currProto.__proto__;
        }
        if(0 !== Object.keys(exceptions).length) {
            let exceptionsArray = [];
            for(let method in exceptions) {
                exceptionsArray.push( exceptions[method] + "." + method);
            }
            exceptionsArray.sort();
            throw new TypeError("Must override the following methods: " + exceptionsArray.join(", "));
        }
    }
    }

Usage:

class MyAbstractClass1 extends AbstractClass {
    static abstractMethods = [
        "myMethod1", // (x:string, y:string): string
        "myMethod2" // (y:string, z:string): string 
    ]
}

class MyAbstractClass2 extends MyAbstractClass1 {
    static abstractMethods = [
        "myMethod3", // (x:string, y:string): string
        "myMethod4" // (y:string, z:string): string 
    ]
}

class MyClass extends MyAbstractClass2 {
    myMethod1(x, y){return "apple"}
}

new MyClass()
//Error
somla
  • 11
  • 3