我发现以下TypeScript片段的行为不一致.我是不是错过了什么



下面,将字符串文本分配给基本(基元)类型string的变量是可以的:let s3: string = "s"

但是TypeScript不应该禁止从字符串文字到非基本String类型的变量的赋值吗:let s1: String = "s"?特别是考虑到后面的s1 instanceof Stringfalse:

TS游乐场链接。

let s1: String = "s"; // no error here
let s2: String = new String("s"); 
let s3: string = "s";
console.log(s1 instanceof String) // false
console.log(s2 instanceof String) // true
console.log(s1 === s2); // false
console.log(s1 === s3); // true;
console.log(typeof(s1)) // "string"
console.log(typeof(s2)) // "object"
console.log(typeof(s3)) // "string"
//console.log(s3 instanceof string) // error

生成的JS代码是这样的(与TS 4.0,-t ESNext t.ts):

let s1 = "s"; // no error here
let s2 = new String("s");
let s3 = "s";
console.log(s1 instanceof String); // false
console.log(s2 instanceof String); // true
console.log(s1 === s2); // false
console.log(s1 === s3); // true;
console.log(typeof (s1)); // "string"
console.log(typeof (s2)); // "object"
console.log(typeof (s3)); // "string"
//console.log(s3 instanceof String) // error

我理解这个JavaScript代码中的工作原理,但为什么TS默认这样生成它:一个基元值,而不是String的隐式实例。我宁愿期待let s1 = new String("s"),或者一个错误。

我的意思是,如果变量v在TypeScript中是非基本类型Type,我希望v instanceof Typetrue,但s1不是这样。

规范中是否定义了这种行为?

首先:TypeScript不能也不会做的一件事是获取类似let s1: String = "s";的代码,并将其作为let s1 = new String("s");发送到JavaScript。这是因为当TypeScript编译为JavaScript时,TypeScript的类型系统会被擦除。TypeScript的发射器将去掉任何类型系统特定的功能,如类型注释。如果在JavaScript的目标版本中剩下的是有效的JavaScript,那么它将按原样发出。对于所发出的JavaScript,除了let s1 = "s";之外,实际上没有其他选择。

TypeScript语言设计的具体非目标是";在程序中添加或依赖运行时类型信息,或根据类型系统的结果发出不同的代码;。因此,应该放弃任何此类建议或期望。


现在:为什么它们允许您将string值分配给String类型的变量?这是microsoft/TypeScript#3448的主题(尽管该问题的开场白认为stringString等类型应该是可相互分配的,而您建议它们应该是相互不兼容的…但会出现相同的主题,因此请参阅该问题以了解更多信息)。

目前,像string这样的基元类型被认为可以分配给为其包装对象类型(如String)定义的接口,但不能反之亦然。也就是说,在TypeScript中,stringString的(适当的)子类型。尽管至少有一位语言设计师将这种情况描述为地雷,但这一切都按预期进行。

那么,为什么上述行为(其中string可分配给String)按预期工作呢?为了理解它,让我们来谈谈TypeScript的一些通常理想的功能,它们结合在一起会导致这种有点不幸的行为。


第一个是命名类构造函数值和它们构造的实例的接口类型之间的关系。假设我们有一个名为Foo的类构造函数;这将在运行时存在(作为显式ES2015class或ES5function),我们可以使用它来构建实例(例如new Foo("someArg");)和针对实例进行测试(例如val instanceof Foo)。然后,一般来说,TypeScript的静态类型系统中会有一个interface,也称为Foo,对应于构造函数创建的实例的类型。因此,如果我们调用const foo = new Foo("someArg");,那么当我们在TypeScript IDE中检查foo时,它可能会显示const foo: Foo;

当我们用TypeScript编写class时,这种同名关系会自动发生。对于不使用class语法的库声明,这也是通常的约定;构造函数将有一个命名接口,名为FooConstructor,具有{ new (arg: string) => Foo }等可更新签名,然后构造函数值将声明为declare var Foo: FooConstructor;(有关更多信息,请参阅手册的这一部分)。。。这相当于相同的事情:CCD_ 43是其实例类型为CCD_。

String(以及NumberBoolean)遵循此约定。有一个StringConstructor接口,并且String被声明为该类型的值。当我们在StringConstructor上调用new String()时,我们会得到一个可分配给String接口的值。


接下来的事情是TypeScript的类型系统是结构,而不是标称。如果类型A和类型B具有相同的形状(即,它们的属性和方法具有相同的名称和类型),则TypeScript将它们视为相同的类型。即使interface A { }interface B { }在两个不同的地方声明,并且彼此不提及,它们仍然可以是同一类型。

这与先前的特征相结合导致instanceof:的行为有些怪异

class Foo {
x: string;
constructor(x: string) {
this.x = x;
}
}
const foo: Foo = new Foo("x");
console.log(foo instanceof Foo); // true
const bar: Foo = { x: "x" }; // also accepted
console.log(bar instanceof Foo); // false

我可以说bar的类型是Foo,因为Foo接口只关心它有一个名为xstring类型的属性。Foo类型的值不需要由Foo构造函数构造。因此val instanceof Foo的行为不能在TypeScript中清晰地表示。JavaScript关心值的来源,而TypeScript不关心,这种不匹配是不幸的,但由于TypeScript依赖于结构兼容性,不容易避免。


最后,每当您试图查看属性或调用它们上的方法时,JavaScript中的基元都会被相应类型的包装器对象包装。这使得像string这样的基元具有作为具有像String这样的接口的对象的外观。因此,TypeScript允许您像对待String一样对待string,方法是从String接口为其提供明显的成员。这就是为什么您可以在TypeScript中编写没有警告的"foo".toUpperCase()


把这三个放在一起,你的问题就会一团糟。TypeScript的CCD_;这个东西具有与CCD_ 73对象"相同的属性和方法;而不是";这个东西是通过在CCD_ 75构造函数上调用CCD_;。没有什么可以阻止您编写const str: String = "x";。当然,typeof str === typeof new String()将是false,并且任何其他行为取决于基元与其包装对象之间差异的测试在TypeScript中都是不可观察的。这是TypeScript的一些不同有用功能以令人不快的方式交互的结果。

因此,建议永远不要使用包装器对象类型。如果你在代码中编写类型String,它可能不是你想要的,所以不要这么做。这样的建议可能很难替代编译器警告,但现在看来这是最好的选择。


到代码的游乐场链接

好吧,这实际上看起来很合乎逻辑的

字符串文字具有与String对象相同的方法,因此例如'a'.splitnew String('a').split都存在,您可以在运行时使用它们,并且不会中断。在typescript中,如果一个对象具有另一个对象所具有的所有属性,则后者可赋值给前者。

关于比较:typescript并不能真正控制它们,它不会像你比较'a'new String('a')那样搜索错误,结果发现它们不等于

关于instanceof:如果定义变量let a: SomeObjectType,表达式a instanceof SomeObjectType将返回true,这并不总是这样。

考虑这个例子:

const obj: Object = {}
Object.setPrototypeOf(obj, null)
console.log(obj instanceof Object) // false

或者更简单:

class A {
foo: boolean
}
// this is fine, because { foo: false } has all properties of A
let obj: A = { foo: false } 
console.log(obj instanceof A) // false

最新更新