且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《鸭子》打字与打字稿中的函数参数

更新时间:2022-10-17 10:14:44

TypeScript 的类型系统是 structural(您称之为鸭子"类型),并且一般来说,额外的属性不被视为违反对象类型的结构.换句话说,TypeScript 中的对象类型是开放/可扩展的".而不是关闭/精确";已知类型 {a: string} 具有字符串值 a 属性,但不知道缺乏其他属性.>

开放对象类型启用诸如interfaceclass 扩展等有用的东西,所以如果Y extends X 那么你可以使用Y 任何可以使用 X 的地方,即使 Y 具有更多功能.

因此,要回答您的第二个问题,该语言中的大多数地方仅依赖于结构子类型.


据我所知,编译器表现为对象类型准确的唯一地方是当您创建新的对象字面量时.编译器假定当您创建一个对象字面量时,您会关心它的所有属性.如果您立即将这样的文字分配给不知道所有对象文字属性的类型,编译器会警告您:编译器将忘记这些额外的属性并且无法跟踪它们,这可能是您的错误部分.这称为过度属性检查.只有当你有一个新鲜"的食物时它才会开始.对象字面量(尚未在任何地方分配)并将其分配给不期望其所有属性的类型.

手册中给出的为什么需要进行此检查的示例涉及拼写错误的可选属性.如果你有一个像 {weird?: boolean } 类型的变量,并将对象字面值 { wierd: true } 分配给它,编译器会说嗯,这个值确实适合该类型.它没有 weird 属性,这很好,因为它是可选的.但是它有这个额外的 wierd 属性,我会立即忘记它;为什么会有人这样做?也许这是一个错误."我不知道你是否同意这个推理,但就是这样.


所以回答你的第一个问题,编译器很满意

const myVar = {姓名:约翰",开心:好"};打印名称(我的变量);

因为对象字面量在其初始赋值中没有加宽(myVar 的类型已知具有 namehappy属性),当您将它传递到 printName() 时,它不再是新鲜的".编译器不会知道 printName() 实现中的 happy 属性,但它知道 中的 happy 属性myVar.

而且很不满意

const myVar: Named = { name: Jim", happy: OK";};

因为它被过度的属性检查所捕获.myVar 的类型将不包含对 happy 的任何引用.


好的,希望有帮助;祝你好运!

Typescript performs "duck" typing in certain circumstances, such as when you are checking the validity of an argument to a function against an interface.

For example:

interface Named {
  name: string
}

function printName(x: Named) {
  return x.name;
}

const myVar = {
  name: "John",
  happy: "OK", // This extra key-value pair does not break our printName function
};

printName(myVar);

However, when you create a variable and define its type, an extra key-value pair will throw a type error:

const myVar: Named = { name: "Jim", extraVal: "Oops" } // The "extraVal" is not allowed.

1) Why does Typescript check for the exact match in the second instance, but not with the parameter passed to the function?

2) Are there other instances when duck-typing is used, and how can one tell these instances apart?

TypeScript's type system is structural (which you're calling "duck" typing), and in general, extra properties are not considered to violate the structure of an object type. In other words, object types in TypeScript are "open/extendible" and not "closed/exact"; the type {a: string} is known to have a string-valued a property, but is not known to lack other properties.

Open object types enable useful things like interface and class extension, so if Y extends X then you can use a Y everywhere you could use an X, even if Y has more functionality.

So to answer your second question, most places in the language rely only on structural subtyping.


As far as I know, the only place where the compiler acts as if object types were exact is when you create a new object literal. The compiler assumes that when you create an object literal that you care about all its properties. If you then immediately assign such a literal to a type that does not know about all the object literal's properties, the compiler warns you: the compiler will forget about these extra properties and be unable to track them, and it might be a mistake on your part. This is called excess property checking. It only kicks in when you have a "fresh" object literal (that has not yet been assigned anywhere) and you assign it to a type that does not expect all its properties.

The example given in the handbook for why this check is desirable involves misspelling optional properties. If you have a variable of a type like { weird?: boolean } and assign to it the object literal { wierd: true }, the compiler says "hmm, this value does fit the type. It has no weird property, which is fine because it's optional. But it has this extra wierd property that I'm going to immediately forget about; why would someone do that? Maybe that's an error." I don't know whether you agree with this reasoning or not, but there it is.


So to answer your first question, the compiler is happy with

const myVar = {
  name: "John",
  happy: "OK"
};    
printName(myVar);

because the object literal is not widened in its initial assignment (the type of myVar is known to have both a name and a happy property), and by the time you pass it into printName(), it's no longer "fresh". The compiler will not know about the happy property inside the implementation of printName(), but it does know about the happy property in myVar.

And it's unhappy with

const myVar: Named = { name: "Jim", happy: "OK" };

because it gets caught by excess property checking. The type of myVar will not contain any reference to happy.


Okay, hope that helps; good luck!