有人在TypeScript中做过构造函数重载吗?在语言规范(v 0.8)的第64页,有描述构造函数重载的语句,但没有给出任何示例代码。

我现在正在尝试一个非常基本的类声明;它是这样的,

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {    
        this.x = obj.x;
        this.y = obj.y;
        this.height = obj.height;
        this.width = obj.width;
    }   

    constructor() {
        this.x = 0;
        this.y = 0;
        this.width = 0;
        this.height = 0;
    }
}

当运行tsc BoxSample。Ts,它抛出一个重复的构造函数定义——这是显而易见的。任何帮助都是感激的。


当前回答

我使用以下替代方法来获得默认/可选参数和“kind-of-overloaded”的构造函数,参数数量可变:

private x?: number;
private y?: number;

constructor({x = 10, y}: {x?: number, y?: number}) {
 this.x = x;
 this.y = y;
}

我知道这不是最漂亮的代码,但人们会习惯它。不需要额外的接口,它允许私有成员,这在使用接口时是不可能的。

其他回答

关于构造函数重载,一个好的选择是将额外的重载实现为静态工厂方法。我认为它比在构造函数中检查所有可能的参数组合更易于阅读和更容易。

在下面的例子中,我们能够使用来自保险提供商的数据创建一个患者对象,这些数据存储的值不同。为了支持患者实例化的另一种数据结构,可以简单地添加另一个静态方法,以便在规范化所提供的数据后尽可能地调用默认构造函数。

class Patient {
    static fromInsurance({
        first, middle = '', last,
        birthday, gender
    }: InsuranceCustomer): Patient {
        return new this(
            `${last}, ${first} ${middle}`.trim(),
            utils.age(birthday),
            gender
        );
    }

    constructor(
        public name: string,
        public age: number,
        public gender?: string
    ) {}
}

interface InsuranceCustomer {
    first: string,
    middle?: string,
    last: string,
    birthday: string,
    gender: 'M' | 'F'
}


const utils = { /* included in the playground link below */};

{// Two ways of creating a Patient instance
    const
        jane = new Patient('Doe, Jane', 21),
        alsoJane = Patient.fromInsurance({ 
            first: 'Jane', last: 'Doe',
            birthday: 'Jan 1, 2000', gender: 'F'
        })

    console.clear()
    console.log(jane)
    console.log(alsoJane)
}

你可以在TS Playground查看输出


TypeScript中的方法重载并不是真的,因为它需要太多的编译器生成的代码,而TS的设计就是为了尽量避免这种情况。方法重载的主要用例可能是为API中有神奇参数的库编写声明。由于处理不同可能的参数集的所有繁重工作都是由您完成的,因此我不认为在每种场景中使用重载而不是特别方法有多大优势。

interface IBox {
    x: number;
    y: number;
    height: number;
    width: number;
}

class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj: IBox) {
        const { x, y, height, width } = { x: 0, y: 0, height: 0, width: 0, ...obj }
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
}

TypeScript允许你声明重载,但是你只能有一个实现,而且这个实现必须有一个与所有重载兼容的签名。在你的例子中,这可以很容易地用一个可选参数来完成,

interface IBox {    
    x : number;
    y : number;
    height : number;
    width : number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

或者使用更通用的构造函数进行两次重载,

interface IBox {    
    x : number;
    y : number;
    height : number;
        width : number;
}
    
class Box {
    public x: number;
    public y: number;
    public height: number;
    public width: number;

    constructor();
    constructor(obj: IBox); 
    constructor(obj?: IBox) {    
        this.x = obj?.x ?? 0
        this.y = obj?.y ?? 0
        this.height = obj?.height ?? 0
        this.width = obj?.width ?? 0;
    }   
}

参见游乐场

你可以通过以下方法来处理:

class Box {
  x: number;
  y: number;
  height: number;
  width: number;
  constructor(obj?: Partial<Box>) {    
     assign(this, obj);
  }
}

Partial将使你的字段(x,y,高度,宽度)可选,允许多个构造函数

例如:你可以新建Box({x,y})没有高度和宽度。

正如chuckj所说,简单的答案是一个可选形参,但是如果我们想用多个形参重载构造函数,或者我们想改变形参顺序,该怎么办呢?

事实证明,构造函数可以像函数一样重载:

class FooBar {
  public foo?: number;
  public bar?: string;

  // Constructor A
  constructor(foo: number, bar?: string);
  // Constructor B
  constructor(bar: string, foo?: number);
  // Constructor C
  constructor(bar: string);
  // Constructor D
  constructor(foo: number);
  // Constructor E
  constructor();

  constructor(...args: any[]) {
    switch (args.length) {
      case 2:
        if (typeof args[0] === "number") {
          this.foo = args[0];
          this.bar = args[1];
        } else {
          this.bar = args[0];
          this.foo = args[1];
        }
        break;
      case 1:
        if (typeof args[0] === "number") {
          this.foo = args[0];
        } else {
          this.bar = args[0];
        }
    }

    console.log(this.foo, this.bar);
  }
}

const fooBarConstructorA = new FooBar("150", 25);
const fooBarConstructorB = new FooBar(25, "150");
const fooBarConstructorC = new FooBar("150");
const fooBarConstructorD = new FooBar("150");
const fooBarConstructorE = new FooBar();