有人在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,它抛出一个重复的构造函数定义——这是显而易见的。任何帮助都是感激的。


当前回答

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

其他回答

更新2(2020年9月28日):这种语言在不断发展,所以如果你可以使用Partial(在v2.1中引入),那么这是我现在最喜欢的实现这一目标的方式。

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

   public constructor(b: Partial<Box> = {}) {
      Object.assign(this, b);
   }
}

// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10});          // Will fail to compile

更新(2017年6月8日):guyarad和snolflake在他们的评论中对我的回答提出了有效的观点。我建议读者看看Benson, Joe和snolflake的答案,他们的答案比我的更好

原答案(2014年1月27日)

另一个如何实现构造函数重载的例子:

class DateHour {

  private date: Date;
  private relativeHour: number;

  constructor(year: number, month: number, day: number, relativeHour: number);
  constructor(date: Date, relativeHour: number);
  constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
    if (typeof dateOrYear === "number") {
      this.date = new Date(dateOrYear, monthOrRelativeHour, day);
      this.relativeHour = relativeHour;
    } else {
      var date = <Date> dateOrYear;
      this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
      this.relativeHour = monthOrRelativeHour;
    }
  }
}

来源:http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript

It sounds like you want the object parameter to be optional, and also each of the properties in the object to be optional. In the example, as provided, overload syntax isn't needed. I wanted to point out some bad practices in some of the answers here. Granted, it's not the smallest possible expression of essentially writing box = { x: 0, y: 87, width: 4, height: 0 }, but this provides all the code hinting niceties you could possibly want from the class as described. This example allows you to call a function with one, some, all, or none of the parameters and still get default values.

 /** @class */
 class Box {
     public x?: number;
     public y?: number;
     public height?: number;
     public width?: number;     

     constructor(params: Box = {} as Box) {

         // Define the properties of the incoming `params` object here. 
         // Setting a default value with the `= 0` syntax is optional for each parameter
         let {
             x = 0,
             y = 0,
             height = 1,
             width = 1
         } = params;
         
         //  If needed, make the parameters publicly accessible
         //  on the class ex.: 'this.var = var'.
         /**  Use jsdoc comments here for inline ide auto-documentation */
         this.x = x;
         this.y = y;
         this.height = height;
         this.width = width;
     }
 }

Need to add methods? A verbose but more extendable alternative: The Box class above can work double-duty as the interface since they are identical. If you choose to modify the above class, you will need to define and reference a new interface for the incoming parameters object since the Box class no longer would look exactly like the incoming parameters. Notice where the question marks (?:) denoting optional properties move in this case. Since we're setting default values within the class, they are guaranteed to be present, yet they are optional within the incoming parameters object:

    interface BoxParams {
        x?: number;
         // Add Parameters ...
    }

    class Box {
         public x: number;
         // Copy Parameters ...
         constructor(params: BoxParams = {} as BoxParams) {
         let { x = 0 } = params;
         this.x = x;
    }
    doSomething = () => {
        return this.x + this.x;
        }
    }

无论你选择哪种方式来定义你的类,这种技术都提供了类型安全的护栏,同时还可以灵活地编写以下任何一种:

const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x:0});
const box4 = new Box({x:0, height:10});
const box5 = new Box({x:0, y:87,width:4,height:0});

 // Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z:0});  

编译后,您将看到如何仅在可选值未定义时使用默认设置;它避免了广泛使用(但容易出错)的回退语法var = isOptional || default的陷阱;通过检查void 0,它是undefined的缩写:

编译后的输出

var Box = (function () {
    function Box(params) {
        if (params === void 0) { params = {}; }
        var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
    return Box;
}());

附录:设置默认值:错误的方式

|| (or)操作符

在设置默认回退值时,如其他一些答案所示,请考虑||/或操作符的危险。下面的代码说明了设置默认值的错误方法。当你对0,",null, undefined, false, NaN等假值进行计算时,你可能会得到意想不到的结果:

var myDesiredValue = 0;
var result = myDesiredValue || 2;

// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);

Object.assign (params)

在我的测试中,使用es6/typescript解构object可以比object .assign快15-90%。使用解构形参只允许您分配给对象的方法和属性。例如,考虑这个方法:

class BoxTest {
    public x?: number = 1;

    constructor(params: BoxTest = {} as BoxTest) {
        Object.assign(this, params);
    }
}

如果另一个用户没有使用TypeScript,并试图放置一个不属于TypeScript的参数,比如,他们可能会尝试放置一个z属性

var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});

// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')

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

我们可以使用守卫来模拟构造函数重载

interface IUser {
  name: string;
  lastName: string;
}

interface IUserRaw {
  UserName: string;
  UserLastName: string;
}

function isUserRaw(user): user is IUserRaw {
  return !!(user.UserName && user.UserLastName);
}

class User {
  name: string;
  lastName: string;

  constructor(data: IUser | IUserRaw) {
    if (isUserRaw(data)) {
      this.name = data.UserName;
      this.lastName = data.UserLastName;
    } else {
      this.name = data.name;
      this.lastName = data.lastName;
    }
  }
}

const user  = new User({ name: "Jhon", lastName: "Doe" })
const user2 = new User({ UserName: "Jhon", UserLastName: "Doe" })

在可选的类型参数足够好的情况下,考虑以下代码,它可以在不重复属性或定义接口的情况下实现相同的功能:

export class Track {
   public title: string;
   public artist: string;
   public lyrics: string;

   constructor(track?: Track) {
     Object.assign(this, track);
   }
}

请记住,这将分配所有传递到track的属性,即使它们没有在track上定义。