【问题标题】:TypeScript function overloadingTypeScript 函数重载
【发布时间】:2012-10-24 03:19:55
【问题描述】:

TypeScript 语言规范的第 6.3 节讨论了函数重载,并给出了如何实现这一点的具体示例。但是,如果我尝试这样的事情:

export class LayerFactory { 

    constructor (public styleFactory: Symbology.StyleFactory) { }

    createFeatureLayer (userContext : Model.UserContext, mapWrapperObj : MapWrapperBase) : any {           
         throw "not implemented";
    }                 

    createFeatureLayer(layerName : string, style : any) : any {
        throw "not implemented";
     }        

}

即使函数参数的类型不同,我也会收到指示重复标识符的编译器错误。即使我向第二个 createFeatureLayer 函数添加了一个附加参数,我仍然会收到编译器错误。请出主意。

【问题讨论】:

标签: typescript overloading


【解决方案1】:

一般来说什么是函数重载?

函数重载或方法重载是通过不同的实现创建同名的多个函数的能力(Wikipedia) p>


什么是JS中的函数重载?

此功能在 JS 中是不可能的 - 最后定义的函数在多个声明的情况下被采用:

function foo(a1, a2) { return `${a1}, ${a2}` }
function foo(a1) { return `${a1}` } // replaces above `foo` declaration
foo(42, "foo") // "42"

...在 TS 中?

Overloads 是一个 compile-time 构造,对 JS 运行时没有影响:

function foo(s: string): string // overload #1 of foo
function foo(s: string, n: number): number // overload #2 of foo
function foo(s: string, n?: number): string | number {/* ... */} // foo implementation

如果您使用上述代码(比 JS 更安全),则会触发重复实现错误。 TS 以自上而下的顺序选择第一个合适的重载,因此重载从最具体到最广泛排序。


TS 中的方法重载:一个更复杂的例子

重载的类方法类型可以以与函数重载类似的方式使用:

class LayerFactory {
    createFeatureLayer(a1: string, a2: number): string
    createFeatureLayer(a1: number, a2: boolean, a3: string): number
    createFeatureLayer(a1: string | number, a2: number | boolean, a3?: string)
        : number | string { /*... your implementation*/ }
}

const fact = new LayerFactory()
fact.createFeatureLayer("foo", 42) // string
fact.createFeatureLayer(3, true, "bar") // number

可能有截然不同的重载,因为函数实现与所有重载签名兼容 - 由编译器强制执行。

更多信息:

【讨论】:

    【解决方案2】:

    打字稿中的函数重载:

    根据维基百科(和许多编程书籍),方法/函数重载的定义如下:

    在某些编程语言中,函数重载或方法 重载是创建多个相同功能的能力 具有不同实现的名称。调用重载函数 将运行适合于该功能的特定实现 调用上下文,允许一个函数调用执行不同的 任务取决于上下文。

    在打字稿中,我们不能根据参数的数量和类型来调用同一函数的不同实现。这是因为TS编译成JS时,JS中的函数有如下特点:

    • JavaScript 函数定义未为其参数指定数据类型
    • JavaScript 函数在调用时不检查参数的数量

    因此,从严格意义上讲,人们可能会争辩说 TS 函数重载不存在。但是,您可以在 TS 代码中做一些可以完美模仿函数重载的事情。

    这是一个例子:

    function add(a: number, b: number, c: number): number;
    function add(a: number, b: number): any;
    function add(a: string, b: string): any;
    
    function add(a: any, b: any, c?: any): any {
      if (c) {
        return a + c;
      }
      if (typeof a === 'string') {
        return `a is ${a}, b is ${b}`;
      } else {
        return a + b;
      }
    }
    

    TS 文档将此方法称为重载,我们所做的基本上是向 TS 编译器提供多个方法签名(可能的参数和类型的描述)。现在 TS 可以判断我们在编译时是否正确调用了函数,如果调用不正确,则会给我们一个错误。

    【讨论】:

      【解决方案3】:

      这可能是因为当这两个函数都编译为 JavaScript 时,它们的签名完全相同。由于 JavaScript 没有类型,我们最终创建了两个接受相同数量参数的函数。因此,TypeScript 限制我们创建此类函数。

      TypeScript 支持基于参数数量的重载,但是如果我们与 OO 语言相比,要遵循的步骤会有些不同。在回答另一个 SO 问题时,有人用一个很好的例子来解释它:Method overloading?。

      基本上,我们所做的是,我们只创建一个函数和许多声明,以便 TypeScript 不会产生编译错误。当这段代码被编译为 JavaScript 时,单独的具体函数将是可见的。由于可以通过传递多个参数来调用 JavaScript 函数,因此它可以正常工作。

      【讨论】:

      • 可以修改语言以支持这一点。理论上,可以生成单独命名并由编译后的 TypeScript 调用的函数实现(例如 createFeatureLayer_1 和 createFeatureLayer_2),然后 createFeatureLayer 可以根据参数的内容确定调用哪一个,以便与 vanilla JavaScript 互操作。
      • 你说的好像 TypeScript 中的重载只能基于参数的数量,而基于类型的重载也是可能的,如 Steve Fenton 的回答所示。
      • 这有点蹩脚; TypeScript 应该真正生成“元函数”,根据传递的内容适当地选择唯一命名的实现。现在情况如何,您可以通过编译器,但您的类型嗅探实现可能不正确。
      • @EzekielVictor TypeScript 如果有一种在运行时检查类型的可靠方法会做到这一点。
      • 这更复杂,它适用于 JavaScript 的类型,但 TS 特定的概念,如接口、types、枚举、泛型等,在运行时会丢失。这也是为什么你不能做someObject instanceof ISomeInterfaceDefinedInTypeScript。
      【解决方案4】:

      作为提醒其他人,我观察到至少正如 WebPack 为 Angular 2 编译的 TypeScript 所表明的那样,您悄悄地得到 overWRITTEN 而不是 overLOADED 方法。

      myComponent {
        method(): { console.info("no args"); },
        method(arg): { console.info("with arg"); }
      }
      

      调用:

      myComponent.method()
      

      似乎执行带参数的方法,默默地忽略无参数版本,输出:

      with arg
      

      【讨论】:

      • 你不能为你的重载声明单独的主体,只能声明不同的签名。
      • 我不确定您使用的是哪个版本的 TypeScript 编译器,但当前版本会针对此类代码发出 Duplicate function implementation 警告。
      【解决方案5】:

      当您在 TypeScript 中重载时,您只有一个具有多个签名的实现。

      class Foo {
          myMethod(a: string);
          myMethod(a: number);
          myMethod(a: number, b: string);
          myMethod(a: any, b?: string) {
              alert(a.toString());
          }
      }
      

      TypeScript 仅将这三个重载识别为方法调用的可能签名,而不是实际的实现。

      在您的情况下,我个人会使用两个名称不同的方法,因为参数中没有足够的通用性,这使得方法体很可能需要有很多“ifs”来决定要做什么。

      TypeScript 1.4

      从 TypeScript 1.4 开始,您通常可以使用联合类型消除对重载的需求。上面的例子可以更好地表达为:

      myMethod(a: string | number, b?: string) {
          alert(a.toString());
      }
      

      a 的类型是“string 或 number”。

      【讨论】:

      • 很好的答案。我只想强调一点,当一个人因为以下原因试图重载时,这可能没有帮助:我想要一个实例,在该实例中使用相同的构造函数,我可以传递一个定义所有预期属性的对象,并在一个实例,传递单个参数:class Foo { constructor(obj) { } constructor (a: number, b: string, c: boolean) {} }
      • 一般来说,我宁愿使用工厂方法来为我创建一个对象——如果你调用Foo.fromObject(obj)和Foo.fromJson(str)等等,就不需要分支。
      • 如果您将这种差异追溯到源头,您通常会发现不需要它。例如,无论如何您都必须输入myNum 或myObj,那么为什么不使用单独的方法并让一切清晰/避免不必要的分支逻辑。
      • 请注意,如果您希望根据参数具有不同的返回类型,则使用联合类型可能会出现问题。如果返回类型始终与参数类型之一匹配,则可以使用泛型解决此问题,但对于其他情况,重载是最好的解决方案。
      • 打字稿重载真的很糟糕
      【解决方案6】:

      您可以声明一个重载函数,方法是将函数声明为具有多个调用签名的类型:

      interface IFoo
      {
          bar: {
              (s: string): number;
              (n: number): string;
          }
      }
      

      然后是:

      var foo1: IFoo = ...;
      
      var n: number = foo1.bar('baz');     // OK
      var s: string = foo1.bar(123);       // OK
      var a: number[] = foo1.bar([1,2,3]); // ERROR
      

      函数的实际定义必须是单数的,并且在内部对其参数执行适当的调度。

      例如,使用一个类(可以实现IFoo,但不是必须的):

      class Foo
      {
          public bar(s: string): number;
          public bar(n: number): string;
          public bar(arg: any): any 
          {
              if (typeof(arg) === 'number')
                  return arg.toString();
              if (typeof(arg) === 'string')
                  return arg.length;
          }
      }
      

      这里有趣的是any 表单被更具体的类型覆盖隐藏。

      var foo2: new Foo();
      
      var n: number = foo2.bar('baz');     // OK
      var s: string = foo2.bar(123);       // OK
      var a: number[] = foo2.bar([1,2,3]); // ERROR
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-27
        • 2017-07-26
        • 1970-01-01
        • 2021-11-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多