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

Javascript 为什么TypeScript中的类允许使用duck类型,javascript,class,typescript,interface,duck-typing,Javascript,Class,Typescript,Interface,Duck Typing,看起来在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()); ru

看起来在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());
但与此同时,我预计会出现编译错误,因为
车辆
任务
没有任何共同之处

可以通过明确的接口定义来实现合理的使用:

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类型看起来相当混乱,并且容易出错。

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

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

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#开始这似乎很疯狂,但在可扩展性方面,这种类型检查方式允许更大的灵活性,一旦你习惯了,你就会看到好处。

现在可以使用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
导出类型Kilos=T&{只读鉴别器:唯一符号};
导出类型磅=T&{只读鉴别器:唯一符号};
导出接口度量衡{
价值:公斤
}
导出接口ImperialWeight{
价值:英镑
}
常量wm:MetricWeight={值:0为千克}
常量wi:ImperialWeight={value:0作为磅数}
wm.value=wi.value;//给出编译器错误
wi.value=wi.value*2;//给出编译器错误
wm.value=wi.value*2;//给出编译器错误
const we:MetricWeight={value:0}//给出编译器错误

因为Anders允许。“我不知道他是否会路过让你知道原因,tho!”穆拉蒂克说。不,如果您将返回类型更改为
number
它仍然可以编译。是的,我检查过了。如果返回类型相同,它就可以工作apparently@ivan我认为typescript将受益于“不可复制的类型”。。。当你提出建议时告诉我,我会支持:)@JonasW。我很确定有一个,同时,简单的解决方案是添加一个私有的,你甚至不需要使用它,所以没有运行时的惩罚,
class Task{private unduckable:true}
就足够了,在运行时
unduckable
不会exist@titian我知道,但这是一个糟糕的解决办法。例如,我使用
类型userID=string
类型groupID=string
来区分这两种类型,因为它们非常相似。“如果能阻止鸭子在那里打字,那就太好了。”乔纳斯说。typescript编译器团队也做了类似的事情,这就是我添加属性以使类型不兼容的想法。从TS编译器代码:
export-type-Path=string&{uuuu-pathBrand:any}为了清晰起见:结构类型和Duck类型或多或少是一回事,这两种类型的对比系统称为标称类型: