检查 JavaScript 文件
这里列举了在 .js 文件中检查工作方式与 .ts 文件相比的一些显著差异。
属性是根据类体内的赋值推断的
ES2015 没有在类上声明属性的方法。属性是动态赋值的,就像对象字面量一样。
在 .js 文件中,编译器根据类体内的属性赋值来推断属性。 属性的类型是在构造函数中给出的类型,除非它没有在那里定义,或者构造函数中的类型是 undefined 或 null。 在这种情况下,类型就是这些赋值中所有右侧值的联合类型。 在构造函数中定义的属性总是被认为存在,而仅在方法、getter 或 setter 中定义的属性则被认为是可选的。
class C {
constructor() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
method() {
this.constructorOnly = false; this.constructorUnknown = "plunkbat"; // ok, constructorUnknown 的类型是 string | undefined
this.methodOnly = "ok"; // ok, 但 methodOnly 也可能是 undefined
}
method2() {
this.methodOnly = true; // 同样 ok, methodOnly 的类型是 string | boolean | undefined
}
}Try如果属性从未在类体中设置,它们被认为是未知的。 如果你的类有只读的属性,请在构造函数中添加一个声明,并使用 JSDoc 注释来指定类型。 如果稍后会初始化,你甚至不必提供值:
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
this.count;
}
}
let c = new C();
c.prop = 0; // OK
c.count = "string";Try构造函数等价于类
在 ES2015 之前,JavaScript 使用构造函数而不是类。 编译器支持这种模式,并将构造函数理解为等价于 ES2015 类。 上面描述的属性推断规则完全相同。
function C() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
C.prototype.method = function () {
this.constructorOnly = false; this.constructorUnknown = "plunkbat"; // OK, 类型是 string | undefined
};Try支持 CommonJS 模块
在 .js 文件中,TypeScript 理解 CommonJS 模块格式。 对 exports 和 module.exports 的赋值被识别为导出声明。 类似地,require 函数调用被识别为模块导入。例如:
// 等同于 `import module "fs"`
const fs = require("fs");
// 等同于 `export function readFile`
module.exports.readFile = function (f) {
return fs.readFileSync(f);
};JavaScript 中的模块支持在语法上比 TypeScript 的模块支持宽容得多。 大多数赋值和声明的组合都是受支持的。
类、函数和对象字面量是命名空间
在 .js 文件中,类是命名空间。 这可以用于嵌套类,例如:
并且,对于 ES2015 之前的代码,它可以用来模拟静态方法:
它也可以用来创建简单的命名空间:
其他变体也是允许的:
// IIFE
var ns = (function (n) {
return n || {};
})();
ns.CONST = 1;
// 默认为全局
var assign =
assign ||
function () {
// 代码放在这里
};
assign.extra = 1;Try对象字面量是开放式的
在 .ts 文件中,初始化变量声明的对象字面量将其类型赋予该声明。 不能添加原始字面量中未指定的新成员。 在 .js 文件中,这条规则被放宽了;对象字面量具有开放式的类型(一个索引签名),允许添加和查找最初未定义的属性。 例如:
对象字面量的行为就好像它们有一个索引签名 [x:string]: any,允许将它们视为开放映射而不是封闭对象。
像其他特殊的 JS 检查行为一样,可以通过为变量指定 JSDoc 类型来更改此行为。例如:
null、undefined 和空数组初始化器是 any 或 any[] 类型
任何用 null 或 undefined 初始化的变量、参数或属性都将具有 any 类型,即使开启了严格空值检查。 任何用 [] 初始化的变量、参数或属性都将具有 any[] 类型,即使开启了严格空值检查。 唯一的例外是如上所述具有多个初始化器的属性。
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");Try函数参数默认为可选
由于在 ES2015 之前的 JavaScript 中没有方法指定参数的可选性,.js 文件中的所有函数参数都被认为是可选的。 允许使用比声明的参数数量少的参数进行调用。
需要注意的是,使用过多参数调用函数是错误的。
例如:
function bar(a, b) {
console.log(a + " " + b);
}
bar(1); // OK,第二个参数被认为是可选的
bar(1, 2);
bar(1, 2, 3); // 错误,参数过多TryJSDoc 注释的函数不在此规则内。 使用 JSDoc 可选参数语法([ ])来表示可选性。例如:
/**
* @param {string} [somebody] - 某人的名字。
*/
function sayHello(somebody) {
if (!somebody) {
somebody = "John Doe";
}
console.log("Hello " + somebody);
}
sayHello();Try根据 arguments 的使用推断可变参数声明
函数体内部引用了 arguments 的函数,会被隐式地认为具有可变参数(即 (...arg: any[]) => any)。使用 JSDoc 的可变参数语法来指定参数的类型。
/** @param {...number} args */
function sum(/* numbers */) {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}Try未指定的类型参数默认为 any
由于在 JavaScript 中没有指定泛型类型参数的自然语法,未指定的类型参数默认为 any。
在 extends 子句中
例如,React.Component 被定义为具有两个类型参数 Props 和 State。 在 .js 文件中,没有合法的方法在 extends 子句中指定这些。默认情况下,类型参数将是 any:
import { Component } from "react";
class MyComponent extends Component {
render() {
this.props.b; // 允许,因为 this.props 是 any 类型
}
}使用 JSDoc @augments 来显式指定类型。例如:
import { Component } from "react";
/**
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
this.props.b; // 错误:{a:number} 上不存在 b
}
}在 JSDoc 引用中
JSDoc 中未指定的类型参数默认为 any:
/** @type{Array} */
var x = [];
x.push(1); // OK
x.push("string"); // OK, x 是 Array<any> 类型
/** @type{Array.<number>} */
var y = [];
y.push(1); // OK
y.push("string"); // 错误,string 不能赋值给 numberTry在函数调用中
调用泛型函数时,使用参数来推断类型参数。有时此过程无法推断出任何类型,这主要是由于缺乏推断源;在这些情况下,类型参数将默认为 any。例如:
var p = new Promise((resolve, reject) => {
reject();
});
p; // Promise<any>;要了解 JSDoc 中所有可用功能,请参阅参考文档。