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⽀持的模式,。
属性的推断来⾃于类内的赋值语句
ES2015没提供声明类属性的⽅法。属性是动态赋值的,就像对象字⾯量⼀样。
在.js⽂件⾥,编译器从类内部的属性赋值语句来推断属性类型。属性的类型是在构造函数⾥赋的值的类型,除⾮它没在构造函数⾥定义或者在构造函数⾥是undefined或null。若是这种情况,类型将会是所有赋的值的类型的联合类型。在构造函数⾥定义的属性会被认为是⼀直存在的,然⽽那些在⽅法,存取器⾥定义的属性被当成可选的。
class C {
constructor() {
}
method() {
}
method2() {
}
}
如果⼀个属性从没在类内设置过,它们会被当成未知的。
如果类的属性只是读取⽤的,那么就在构造函数⾥⽤JSDoc声明它的类型。如果它稍后会被初始化,你甚⾄都不需要在构造函数⾥给它赋值:
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
}
}
let c = new C();
c.prop = 0;          // OK
构造函数等同于类
ES2015以前,Javascript使⽤构造函数代替类。编译器⽀持这种模式并能够将构造函数识别为ES2015的类。属性类型推断机制和上⾯介绍的⼀致。
function C() {
}
hod = function() {
}
⽀持CommonJS模块
在.js⽂件⾥,TypeScript能识别出CommonJS模块。对exports和ports的赋值被识别为导出声明。相似地,require函数调⽤被识别为模块导⼊。例如:
// same as `import module "fs"`
const fs = require("fs");
// same as `export function readFile`
adFileSync(f);
}
对JavaScript⽂件⾥模块语法的⽀持⽐在TypeScript⾥宽泛多了。⼤部分的赋值和声明⽅式都是允许的。
类,函数和对象字⾯量是命名空间
.js⽂件⾥的类是命名空间。它可以⽤于嵌套类,⽐如:
class C {
}
C.D = class {
}
ES2015之前的代码,它可以⽤来模拟静态⽅法:
function Outer() {
this.y = 2
}
Outer.Inner = function() {
< = 2
}
它还可以⽤于创建简单的命名空间:
var ns = {}
ns.C = class {
}
ns.func = function() {
}
同时还⽀持其它的变化:
// ⽴即调⽤的函数表达式
var ns = (function (n) {
return n || {};
})();
ns.CONST = 1
// defaulting to global
var assign = assign || function() {
// code goes here
}
对象字⾯量是开放的
.ts⽂件⾥,⽤对象字⾯量初始化⼀个变量的同时也给它声明了类型。新的成员不能再被添加到对象字⾯
量中。这个规则在.js⽂件⾥被放宽了;对象字⾯量具有开放的类型,允许添加并访问原先没有定义的属性。例如:
var obj = { a: 1 };
obj.b = 2;  // Allowed
对象字⾯量的表现就好⽐具有⼀个默认的索引签名[x:string]: any,它们可以被当成开放的映射⽽不是封闭的对象。
与其它JS检查⾏为相似,这种⾏为可以通过指定JSDoc类型来改变,例如:
/** @type {{a: number}} */
var obj = { a: 1 };
obj.b = 2;  // Error, type {a: number} does not have property b
null,undefined,和空数组的类型是any或any[]
任何⽤null,undefined初始化的变量,参数或属性,它们的类型是any,就算是在严格null检查模式下。
任何⽤[]初始化的变量,参数或属性,它们的类型是any[],就算是在严格null检查模式下。唯⼀的例外是像上⾯那样有多个初始化器的属性。
function Foo(i = null) {
if (!i) i = 1;
var j = undefined;
j = 2;
this.l = [];
}
var foo = new Foo();
foo.l.push(foo.i);
foo.l.push("end");
函数参数是默认可选的
由于在ES2015之前⽆法指定可选参数,因此.js⽂件⾥所有函数参数都被当做是可选的。使⽤⽐预期少的参数调⽤函数是允许的。
需要注意的⼀点是,使⽤过多的参数调⽤函数会得到⼀个错误。
例如:
function bar(a, b) {
console.log(a + " " + b);
}
bar(1);      // OK, second argument considered optional
bar(1, 2);
bar(1, 2, 3); // Error, too many arguments
使⽤JSDoc注解的函数会被从这条规则⾥移除。使⽤JSDoc可选参数语法来表⽰可选性。⽐如:
/**
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(somebody) {
if (!somebody) {
somebody = 'John Doe';
}
console.log('Hello ' + somebody);
}
sayHello();
由arguments推断出的var-args参数声明
如果⼀个函数的函数体内有对arguments的引⽤,那么这个函数会隐式地被认为具有⼀个var-arg参数(⽐如:(...arg: any[]) => any))。使⽤JSDoc 的var-arg语法来指定arguments的类型。
/** @param {...number} args */
function sum(/* numbers */) {
var total = 0
for (var i = 0; i < arguments.length; i++) {
total += arguments[i]
}
return total
}
未指定的类型参数默认为any
由于JavaScript⾥没有⼀种⾃然的语法来指定泛型参数,因此未指定的参数类型默认为any。
在extends语句中:
例如,React.Component被定义成具有两个类型参数,Props和State。在⼀个.js⽂件⾥,没有⼀个合法的⽅式在extends语句⾥指定它们。默认地参数类型为any:
import { Component } from "react";
class MyComponent extends Component {
render() {
this.props.b; // Allowed, since this.props is of type any
}
}
使⽤JSDoc的@augments来明确地指定类型。例如:
import { Component } from "react";
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
this.props.b; // Error: b does not exist on {a:number}
}
}
在JSDoc引⽤中:
JSDoc⾥未指定的类型参数默认为any:
/** @type{Array} */
var x = [];
x.push(1);        // OK
x.push("string"); // OK, x is of type Array<any>
/
** @type{Array.<number>} */
var y = [];
y.push(1);        // OK
y.push("string"); // Error, string is not assignable to number
在函数调⽤中
泛型函数的调⽤使⽤arguments来推断泛型参数。有时候,这个流程不能够推断出类型,⼤多是因为缺少推断的源;在这种情况下,类型参数类型默认为any。例如:
var p = new Promise((resolve, reject) => { reject() });
p; // Promise<any>;
⽀持的JSDoc
下⾯的列表列出了当前所⽀持的JSDoc注解,你可以⽤它们在JavaScript⽂件⾥添加类型信息。
注意,没有在下⾯列出的标记(例如@async)都是还不⽀持的。
@type
@param (or @arg or @argument)
@returns (or @return)
@typedef
@callback
@template
@class (or @constructor)
@this
@extends (or @augments)
@enum
它们代表的意义与上⾯给出的通常是⼀致的或者是它的超集。下⾯的代码描述了它们的区别并给出了⼀些⽰例。
@type
可以使⽤@type标记并引⽤⼀个类型名称(原始类型,TypeScript⾥声明的类型,或在JSDoc⾥@typedef标记指定的)可以使⽤任何TypeScript类型和⼤多数JSDoc类型。
/**
* @type {string}
*/
js assignvar s;
/** @type {Window} */
var win;
/** @type {PromiseLike<string>} */
var promisedString;
// You can specify an HTML Element with DOM properties
/** @type {HTMLElement} */
var myElement = document.querySelector(selector);
Data = '';
@type可以指定联合类型—例如,string和boolean类型的联合。
* @type {(string | boolean)}
*/
var sb;
注意,括号是可选的。
/**
* @type {string | boolean}
*/
var sb;
有多种⽅式来指定数组类型:
/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;
还可以指定对象字⾯量类型。例如,⼀个带有a(字符串)和b(数字)属性的对象,使⽤下⾯的语法:
/** @type {{ a: string, b: number }} */
var var9;
可以使⽤字符串和数字索引签名来指定map-like和array-like的对象,使⽤标准的JSDoc语法或者TypeScript语法。/**
* A map-like object that maps arbitrary `string` properties to `number`s.
*
* @type {Object.<string, number>}
*/
var stringToNumber;
/** @type {Object.<number, object>} */
var arrayLike;
这两个类型与TypeScript⾥的{ [x: string]: number }和{ [x: number]: any }是等同的。编译器能识别出这两种语法。
可以使⽤TypeScript或Closure语法指定函数类型。
/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;
或者直接使⽤未指定的Function类型:
/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;
Closure的其它类型也可以使⽤:
/**
* @type {*} - can be 'any' type
*/
var star;
/**
* @type {?} - unknown type (same as 'any')
*/
var question;
转换
TypeScript借鉴了Closure⾥的转换语法。在括号表达式前⾯使⽤@type标记,可以将⼀种类型转换成另⼀种类型/**
* @type {number | string}
*/
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)
导⼊类型
可以使⽤导⼊类型从其它⽂件中导⼊声明。这个语法是TypeScript特有的,与JSDoc标准不同:
/**
* @param p { import("./a").Pet }