JavaScript文件里的类型检查
TypeScript 2.3以后的版本支持使用--checkJs对.js文件进行类型检查和错误提示。
你可以通过添加// @ts-nocheck注释来忽略类型检查;相反,你可以通过去掉--checkJs设置并添加一个// @ts-check注释来选则检查某些.js文件。 你还可以使用// @ts-ignore来忽略本行的错误。 如果你使用了tsconfig.json,JS检查将遵照一些严格检查标记,如noImplicitAny,strictNullChecks等。 但因为JS检查是相对宽松的,在使用严格标记时可能会有些出乎意料的情况。
对比.js文件和.ts文件在类型检查上的差异,有如下几点需要注意:
用JSDoc类型表示类型信息
.js文件里,类型可以和在.ts文件里一样被推断出来。 同样地,当类型不能被推断时,它们可以通过JSDoc来指定,就好比在.ts文件里那样。 如同TypeScript,--noImplicitAny会在编译器无法推断类型的位置报错。 (除了对象字面量的情况;后面会详细介绍)
JSDoc注解修饰的声明会被设置为这个声明的类型。比如:
/** @type {number} */
var x;
x = 0; // OK
x = false; // Error: boolean is not assignable to number你可以在这里找到所有JSDoc支持的模式,JSDoc文档。
属性的推断来自于类内的赋值语句
ES2015没提供声明类属性的方法。属性是动态赋值的,就像对象字面量一样。
在.js文件里,编译器从类内部的属性赋值语句来推断属性类型。 属性的类型是在构造函数里赋的值的类型,除非它没在构造函数里定义或者在构造函数里是undefined或null。 若是这种情况,类型将会是所有赋的值的类型的联合类型。 在构造函数里定义的属性会被认为是一直存在的,然而那些在方法,存取器里定义的属性被当成可选的。
class C {
constructor() {
this.constructorOnly = 0
this.constructorUnknown = undefined
}
method() {
this.constructorOnly = false // error, constructorOnly is a number
this.constructorUnknown = "plunkbat" // ok, constructorUnknown is string | undefined
this.methodOnly = 'ok' // ok, but methodOnly could also be undefined
}
method2() {
this.methodOnly = true // also, ok, methodOnly's type is string | boolean | undefined
}
}如果一个属性从没在类内设置过,它们会被当成未知的。
如果类的属性只是读取用的,那么就在构造函数里用JSDoc声明它的类型。 如果它稍后会被初始化,你甚至都不需要在构造函数里给它赋值:
构造函数等同于类
ES2015以前,Javascript使用构造函数代替类。 编译器支持这种模式并能够将构造函数识别为ES2015的类。 属性类型推断机制和上面介绍的一致。
支持CommonJS模块
在.js文件里,TypeScript能识别出CommonJS模块。 对exports和module.exports的赋值被识别为导出声明。 相似地,require函数调用被识别为模块导入。例如:
对JavaScript文件里模块语法的支持比在TypeScript里宽泛多了。 大部分的赋值和声明方式都是允许的。
类,函数和对象字面量是命名空间
.js文件里的类是命名空间。 它可以用于嵌套类,比如:
ES2015之前的代码,它可以用来模拟静态方法:
它还可以用于创建简单的命名空间:
同时还支持其它的变化:
对象字面量是开放的
.ts文件里,用对象字面量初始化一个变量的同时也给它声明了类型。 新的成员不能再被添加到对象字面量中。 这个规则在.js文件里被放宽了;对象字面量具有开放的类型,允许添加并访问原先没有定义的属性。例如:
对象字面量的表现就好比具有一个默认的索引签名[x:string]: any,它们可以被当成开放的映射而不是封闭的对象。
与其它JS检查行为相似,这种行为可以通过指定JSDoc类型来改变,例如:
null,undefined,和空数组的类型是any或any[]
任何用null,undefined初始化的变量,参数或属性,它们的类型是any,就算是在严格null检查模式下。 任何用[]初始化的变量,参数或属性,它们的类型是any[],就算是在严格null检查模式下。 唯一的例外是像上面那样有多个初始化器的属性。
函数参数是默认可选的
由于在ES2015之前无法指定可选参数,因此.js文件里所有函数参数都被当做是可选的。 使用比预期少的参数调用函数是允许的。
需要注意的一点是,使用过多的参数调用函数会得到一个错误。
例如:
使用JSDoc注解的函数会被从这条规则里移除。 使用JSDoc可选参数语法来表示可选性。比如:
由arguments推断出的var-args参数声明
arguments推断出的var-args参数声明如果一个函数的函数体内有对arguments的引用,那么这个函数会隐式地被认为具有一个var-arg参数(比如:(...arg: any[]) => any))。使用JSDoc的var-arg语法来指定arguments的类型。
未指定的类型参数默认为any
any由于JavaScript里没有一种自然的语法来指定泛型参数,因此未指定的参数类型默认为any。
在extends语句中:
例如,React.Component被定义成具有两个类型参数,Props和State。 在一个.js文件里,没有一个合法的方式在extends语句里指定它们。默认地参数类型为any:
使用JSDoc的@augments来明确地指定类型。例如:
在JSDoc引用中:
JSDoc里未指定的类型参数默认为any:
在函数调用中
泛型函数的调用使用arguments来推断泛型参数。有时候,这个流程不能够推断出类型,大多是因为缺少推断的源;在这种情况下,类型参数类型默认为any。例如:
支持的JSDoc
下面的列表列出了当前所支持的JSDoc注解,你可以用它们在JavaScript文件里添加类型信息。
注意,没有在下面列出的标记(例如@async)都是还不支持的。
@type@param(or@argor@argument)@returns(or@return)@typedef@callback@template@class(or@constructor)@this@extends(or@augments)@enum
它们代表的意义与usejsdoc.org上面给出的通常是一致的或者是它的超集。 下面的代码描述了它们的区别并给出了一些示例。
@type
@type可以使用@type标记并引用一个类型名称(原始类型,TypeScript里声明的类型,或在JSDoc里@typedef标记指定的) 可以使用任何TypeScript类型和大多数JSDoc类型。
@type可以指定联合类型—例如,string和boolean类型的联合。
注意,括号是可选的。
有多种方式来指定数组类型:
还可以指定对象字面量类型。 例如,一个带有a(字符串)和b(数字)属性的对象,使用下面的语法:
可以使用字符串和数字索引签名来指定map-like和array-like的对象,使用标准的JSDoc语法或者TypeScript语法。
这两个类型与TypeScript里的{ [x: string]: number }和{ [x: number]: any }是等同的。编译器能识别出这两种语法。
可以使用TypeScript或Closure语法指定函数类型。
或者直接使用未指定的Function类型:
Closure的其它类型也可以使用:
转换
TypeScript借鉴了Closure里的转换语法。 在括号表达式前面使用@type标记,可以将一种类型转换成另一种类型
导入类型
可以使用导入类型从其它文件中导入声明。 这个语法是TypeScript特有的,与JSDoc标准不同:
导入类型也可以使用在类型别名声明中:
导入类型可以用在从模块中得到一个值的类型。
@param和@returns
@param和@returns@param语法和@type相同,但增加了一个参数名。 使用[]可以把参数声明为可选的:
函数的返回值类型也是类似的:
@typedef, @callback, 和 @param
@typedef, @callback, 和 @param@typedef可以用来声明复杂类型。 和@param类似的语法。
可以在第一行上使用object或Object。
@param允许使用相似的语法。 注意,嵌套的属性名必须使用参数名做为前缀:
@callback与@typedef相似,但它指定函数类型而不是对象类型:
当然,所有这些类型都可以使用TypeScript的语法@typedef在一行上声明:
@template
@template使用@template声明泛型:
用逗号或多个标记来声明多个类型参数:
还可以在参数名前指定类型约束。 只有列表的第一项类型参数会被约束:
@constructor
@constructor编译器通过this属性的赋值来推断构造函数,但你可以让检查更严格提示更友好,你可以添加一个@constructor标记:
通过@constructor,this将在构造函数C里被检查,因此你在initialize方法里得到一个提示,如果你传入一个数字你还将得到一个错误提示。如果你直接调用C而不是构造它,也会得到一个错误。
不幸的是,这意味着那些既能构造也能直接调用的构造函数不能使用@constructor。
@this
@this编译器通常可以通过上下文来推断出this的类型。但你可以使用@this来明确指定它的类型:
@extends
@extends当JavaScript类继承了一个基类,无处指定类型参数的类型。而@extends标记提供了这样一种方式:
注意@extends只作用于类。当前,无法实现构造函数继承类的情况。
@enum
@enum@enum标记允许你创建一个对象字面量,它的成员都有确定的类型。不同于JavaScript里大多数的对象字面量,它不允许添加额外成员。
注意@enum与TypeScript的@enum大不相同,它更加简单。然而,不同于TypeScript的枚举,@enum可以是任何类型:
更多示例
已知不支持的模式
在值空间中将对象视为类型是不可以的,除非对象创建了类型,如构造函数。
对象字面量属性上的=后缀不能指定这个属性是可选的:
Nullable类型只在启用了strictNullChecks检查时才启作用:
Non-nullable类型没有意义,以其原类型对待:
不同于JSDoc类型系统,TypeScript只允许将类型标记为包不包含null。 没有明确的Non-nullable -- 如果启用了strictNullChecks,那么number是非null的。 如果没有启用,那么number是可以为null的。
Last updated
Was this helpful?