如何在 JavaScript 中创建抽象基类,使其无法被实例化

42

我有一个班级

function Node() {
    //implementation
}

另一个类

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

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

如何让类 Node() 无法被实例化?例如,当我尝试

var node = new Node();

那么它会抛出异常吗?


在构造函数中放置throw new Exception(),也许可以吗?我不确定在Javascript中是否有效。 - anon
通常在构造函数中设置原型属性并不是一个好主意。 - Pointy
5个回答

57

在支持ECMAScript 2015(又称ES6)类语法的JavaScript引擎中,可以使用new.target元属性来实现:

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

或者使用类语法:

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

无论哪种情况,只需将AttributionalNode定义为:

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

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

要了解更详细的关于new.target的说明,请参见本文档的第4.2节。


2
你能提供关于 new.target 的参考资料吗? - user663031
1
如果您想知道它是否支持:https://kangax.github.io/compat-table/es6/#new.target。目前还不支持,如所述。 - Ciro Costa
1
MDN: new.target - zypA13510
2
new.targetthis.constructor 之间有什么区别吗? - Donald Duck
@DonaldDuck 可能不会,但有可能存在 this 尚不可用的情况。 - jjmerelo

17

这样做是可行的:

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();

注意:在构造函数内部不能引用this.prototype,因为原型只是构造函数的一个属性,而不是实例的属性。

另外,点击这里可以查看有关如何正确扩展 JS 类的好文章。


当某个类试图从Node()继承时,会出现错误。 - Alexandr Sargsyan
2
@AlexandrSargsyan 不是这样的。我更新了示例来证明这一点。 - levi

8

借鉴@levi的答案,您可以采用类似的解决方案来在今天使用ES6(因为new.target还没有确立):

您可以在Babel的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();

4

虽然这个问题有一个javascript标签,但现在很多项目都在使用在 JS 上的typescript,值得注意的是,TS原生支持抽象类和方法(link)

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log("roaming the earth...");
    }
}

1
根据这些评论,我写了这个。
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(", "));
        }
    }
    }

使用方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接