代码之家  ›  专栏  ›  技术社区  ›  Ivan Velichko

为什么TypeScript中的类允许使用duck类型

  •  28
  • Ivan Velichko  · 技术社区  · 8 年前

    看起来在TypeScript中,拥有这样的代码(从编译器的角度来看)是绝对好的:

    class Vehicle {
        public run(): void { console.log('Vehicle.run'); }
    }
    
    class Task {
        public run(): void { console.log('Task.run'); }
    }
    
    function runTask(t: Task) {
        t.run();
    }
    
    runTask(new Task());
    runTask(new Vehicle());
    

    但同时我希望 编译错误 因为 Vehicle Task 没有任何共同点。

    精神健全的 可通过明确的接口定义实现用途:

    interface Runnable {
        run(): void;
    }
    
    class Vehicle implements Runnable {
        public run(): void { console.log('Vehicle.run'); }
    }
    
    class Task implements Runnable {
        public run(): void { console.log('Task.run'); }
    }
    
    function runRunnable(r: Runnable) {
        r.run();
    }
    
    runRunnable(new Task());
    runRunnable(new Vehicle());
    

    ... 或公共父对象:

    class Entity {
        abstract run(): void;
    }
    
    class Vehicle extends Entity {
        public run(): void { console.log('Vehicle.run'); }
    }
    
    class Task extends Entity {
        public run(): void { console.log('Task.run'); }
    }
    
    function runEntity(e: Entity) {
        e.run();
    }
    
    runEntity(new Task());
    runEntity(new Vehicle());
    

    是的,对于JavaScript来说,有这样的行为是绝对好的,因为根本没有类,也没有编译器(只有语法糖),而duck类型对于语言来说是很自然的。但TypeScript试图引入静态检查、类、接口等。然而,在我看来,类实例的duck类型看起来相当混乱,而且容易出错。

    2 回复  |  直到 8 年前
        1
  •  28
  •   Titian Cernicova-Dragomir    8 年前

    这就是结构类型的工作方式。Typescript有一个结构类型系统,可以最好地模拟Javscript的工作方式。由于Javascript使用duck类型,因此定义契约的任何对象都可以在任何函数中使用。Typescript只是尝试在编译时而不是在运行时验证duck类型。

    然而,您的问题只会在普通类中出现,一旦您添加了私有类,即使它们具有相同的结构,类也会变得不兼容:

    class Vehicle {
        private x: string;
        public run(): void { console.log('Vehicle.run'); }
    }
    
    class Task {
        private x: string;
        public run(): void { console.log('Task.run'); }
    }
    
    function runTask(t: Task) {
        t.run();
    }
    
    runTask(new Task());
    runTask(new Vehicle()); // Will be a compile time error
    

    此行为还允许您不显式实现接口,例如,您可以使用函数为参数内联定义接口,并且任何满足约定的类都将兼容,即使它们没有显式实现任何接口:

    function runTask(t: {  run(): void }) {
        t.run();
    }
    
    runTask(new Task());
    runTask(new Vehicle());
    

    就个人而言,从C开始,这似乎很疯狂,但在可扩展性方面,这种类型检查方式允许更大的灵活性,一旦习惯了,就会看到好处。

        2
  •  6
  •   Lu4    7 年前

    现在可以使用TypeScript创建标称类型,允许您根据上下文区分类型。请考虑以下问题:

    Atomic type discrimination (nominal atomic types) in TypeScript

    举个例子:

    export type Kilos<T> = T & { readonly discriminator: unique symbol };
    export type Pounds<T> = T & { readonly discriminator: unique symbol };
    
    export interface MetricWeight {
        value: Kilos<number>
    }
    
    export interface ImperialWeight {
        value: Pounds<number>
    }
    
    const wm: MetricWeight = { value: 0 as Kilos<number> }
    const wi: ImperialWeight = { value: 0 as Pounds<number> }
    
    wm.value = wi.value;                  // Gives compiler error
    wi.value = wi.value * 2;              // Gives compiler error
    wm.value = wi.value * 2;              // Gives compiler error
    const we: MetricWeight = { value: 0 } // Gives compiler error
    
    推荐文章