Javascript 我发现以下类型脚本片段的行为不一致。我错过什么了吗?

Javascript 我发现以下类型脚本片段的行为不一致。我错过什么了吗?,javascript,typescript,Javascript,Typescript,在下面,将字符串文本分配给基本(基本)类型的变量string很好:let s3:string=“s” 但是TypeScript不应该禁止从字符串文本到非基本stringtype:let s1:string=“s”变量的赋值吗?特别是,考虑到后面的s1 instanceof String是false: 生成的JS代码如下(使用TS 4.0,-t ESNext t.TS): 我理解JavaScript代码中的工作原理,但为什么TS默认生成这样的代码:一个原始值,而不是String的隐式实例。我更希

在下面,将字符串文本分配给基本(基本)类型的变量
string
很好:
let s3:string=“s”

但是TypeScript不应该禁止从字符串文本到非基本
string
type:
let s1:string=“s”
变量的赋值吗?特别是,考虑到后面的
s1 instanceof String
false

生成的JS代码如下(使用TS 4.0,
-t ESNext t.TS
):

我理解JavaScript代码中的工作原理,但为什么TS默认生成这样的代码:一个原始值,而不是
String
的隐式实例。我更希望s1=newstring(“s”),或者出现错误

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


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

嗯,这实际上似乎很合乎逻辑

字符串文本具有与
String
对象相同的所有方法,例如
'a'.split
new String('a')。split
都存在,您可以在运行时使用它们,而不会出现任何中断。在typescript中,如果一个对象拥有另一个对象拥有的所有属性,则后者可分配给前者

关于比较:typescript并不真正控制它们,它不会搜索错误,比如比较
'a'
新字符串('a')
,发现它们不相等

关于
instanceof
:如果定义变量
让a:SomeObjectType
,表达式
SomeObjectType
的instanceof将返回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

首先:TypeScript不能也不会做的一件事是使用类似于
let s1:String=“s”的代码
并将其作为
let s1=新字符串(“s”)发送到JavaScript。这是因为TypeScript的类型系统是在将TypeScript编译为JavaScript时建立的。TypeScript的发射器将去除任何类型系统特定的功能,如类型注释。如果剩下的是目标版本JavaScript中的有效JavaScript,则将按原样发出。除了
let s1=“s”之外,真的没有其他选择用于发出的JavaScript

特别是“在程序中添加或依赖运行时类型信息,或根据类型系统的结果发出不同的代码”。因此,任何这类建议或期望都应该放弃


现在:为什么它们允许您将
string
值分配给
string
类型的变量?这就是问题的主题(尽管该问题的开篇者认为像
string
string
这样的类型应该是相互可分配的,而您的建议是它们应该是相互不兼容的……但是出现了相同的主题,因此有关更多信息,请参阅该问题)

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

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


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

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

字符串
(和
数字
布尔值
)遵循此约定。存在一个字符串,
字符串
被声明为该类型的值。当我们在
StringConstructor
上调用
newstring()
时,我们会得到一个可分配给


接下来,TypeScript的类型系统是非标称的。如果类型
A
和类型
B
具有相同的形状(即,它们的属性和方法具有相同的名称和类型),则TypeScript将它们视为相同的类型。即使
接口A{}
接口B{}
在两个不同的位置声明,并且没有相互提及,它们也可以
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
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