• JavaScript文件类型检查
    • 用JSDoc类型表示类型信息
    • 属性的推断来自于类内的赋值语句
    • 构造函数等同于类
    • 支持CommonJS模块
    • 类,函数和对象字面量是命名空间
    • 对象字面量是开放的
    • null,undefined,和空数组的类型是any或any[]
    • 函数参数是默认可选的
    • 由arguments推断出的var-args参数声明
    • 未指定的类型参数默认为any
      • 在extends语句中:
      • 在JSDoc引用中:
      • 在函数调用中
  • 支持的JSDoc
    • @type
      • 转换
      • 导入类型
    • @param和@returns
    • @typedef, @callback, 和 @param
    • @template
    • @constructor
    • @this
    • @extends
    • @enum
    • 更多示例
    • 已知不支持的模式

    JavaScript文件类型检查

    TypeScript 2.3以后的版本支持使用—checkJs.js文件进行类型检查和错误提示。

    你可以通过添加// @ts-nocheck注释来忽略类型检查;相反,你可以通过去掉—checkJs设置并添加一个// @ts-check注释来选则检查某些.js文件。你还可以使用// @ts-ignore来忽略本行的错误。如果你使用了tsconfig.json,JS检查将遵照一些严格检查标记,如noImplicitAnystrictNullChecks等。但因为JS检查是相对宽松的,在使用严格标记时可能会有些出乎意料的情况。

    对比.js文件和.ts文件在类型检查上的差异,有如下几点需要注意:

    用JSDoc类型表示类型信息

    .js文件里,类型可以和在.ts文件里一样被推断出来。同样地,当类型不能被推断时,它们可以通过JSDoc来指定,就好比在.ts文件里那样。如同TypeScript,—noImplicitAny会在编译器无法推断类型的位置报错。(除了对象字面量的情况;后面会详细介绍)

    JSDoc注解修饰的声明会被设置为这个声明的类型。比如:

    1. /** @type {number} */
    2. var x;
    3. x = 0; // OK
    4. x = false; // Error: boolean is not assignable to number

    你可以在这里找到所有JSDoc支持的模式,JSDoc文档。

    属性的推断来自于类内的赋值语句

    ES2015没提供声明类属性的方法。属性是动态赋值的,就像对象字面量一样。

    .js文件里,编译器从类内部的属性赋值语句来推断属性类型。属性的类型是在构造函数里赋的值的类型,除非它没在构造函数里定义或者在构造函数里是undefinednull。若是这种情况,类型将会是所有赋的值的类型的联合类型。在构造函数里定义的属性会被认为是一直存在的,然而那些在方法,存取器里定义的属性被当成可选的。

    1. class C {
    2. constructor() {
    3. this.constructorOnly = 0
    4. this.constructorUnknown = undefined
    5. }
    6. method() {
    7. this.constructorOnly = false // error, constructorOnly is a number
    8. this.constructorUnknown = "plunkbat" // ok, constructorUnknown is string | undefined
    9. this.methodOnly = 'ok' // ok, but y could also be undefined
    10. }
    11. method2() {
    12. this.methodOnly = true // also, ok, y's type is string | boolean | undefined
    13. }
    14. }

    如果一个属性从没在类内设置过,它们会被当成未知的。

    如果类的属性只是读取用的,那么就在构造函数里用JSDoc声明它的类型。如果它稍后会被初始化,你甚至都不需要在构造函数里给它赋值:

    1. class C {
    2. constructor() {
    3. /** @type {number | undefined} */
    4. this.prop = undefined;
    5. /** @type {number | undefined} */
    6. this.count;
    7. }
    8. }
    9. let c = new C();
    10. c.prop = 0; // OK
    11. c.count = "string"; // Error: string is not assignable to number|undefined

    构造函数等同于类

    ES2015以前,Javascript使用构造函数代替类。编译器支持这种模式并能够将构造函数识别为ES2015的类。属性类型推断机制和上面介绍的一致。

    1. function C() {
    2. this.constructorOnly = 0
    3. this.constructorUnknown = undefined
    4. }
    5. C.prototype.method = function() {
    6. this.constructorOnly = false // error
    7. this.constructorUnknown = "plunkbat" // OK, the type is string | undefined
    8. }

    支持CommonJS模块

    .js文件里,TypeScript能识别出CommonJS模块。对exportsmodule.exports的赋值被识别为导出声明。相似地,require函数调用被识别为模块导入。例如:

    1. // same as `import module "fs"`
    2. const fs = require("fs");
    3. // same as `export function readFile`
    4. module.exports.readFile = function(f) {
    5. return fs.readFileSync(f);
    6. }

    对JavaScript文件里模块语法的支持比在TypeScript里宽泛多了。大部分的赋值和声明方式都是允许的。

    类,函数和对象字面量是命名空间

    .js文件里的类是命名空间。它可以用于嵌套类,比如:

    1. class C {
    2. }
    3. C.D = class {
    4. }

    ES2015之前的代码,它可以用来模拟静态方法:

    1. function Outer() {
    2. this.y = 2
    3. }
    4. Outer.Inner = function() {
    5. this.yy = 2
    6. }

    它还可以用于创建简单的命名空间:

    1. var ns = {}
    2. ns.C = class {
    3. }
    4. ns.func = function() {
    5. }

    同时还支持其它的变化:

    1. // 立即调用的函数表达式
    2. var ns = (function (n) {
    3. return n || {};
    4. })();
    5. ns.CONST = 1
    6. // defaulting to global
    7. var assign = assign || function() {
    8. // code goes here
    9. }
    10. assign.extra = 1

    对象字面量是开放的

    .ts文件里,用对象字面量初始化一个变量的同时也给它声明了类型。新的成员不能再被添加到对象字面量中。这个规则在.js文件里被放宽了;对象字面量具有开放的类型,允许添加并访问原先没有定义的属性。例如:

    1. var obj = { a: 1 };
    2. obj.b = 2; // Allowed

    对象字面量的表现就好比具有一个默认的索引签名[x:string]: any,它们可以被当成开放的映射而不是封闭的对象。

    与其它JS检查行为相似,这种行为可以通过指定JSDoc类型来改变,例如:

    1. /** @type {{a: number}} */
    2. var obj = { a: 1 };
    3. obj.b = 2; // Error, type {a: number} does not have property b

    null,undefined,和空数组的类型是any或any[]

    任何用nullundefined初始化的变量,参数或属性,它们的类型是any,就算是在严格null检查模式下。任何用[]初始化的变量,参数或属性,它们的类型是any[],就算是在严格null检查模式下。唯一的例外是像上面那样有多个初始化器的属性。

    1. function Foo(i = null) {
    2. if (!i) i = 1;
    3. var j = undefined;
    4. j = 2;
    5. this.l = [];
    6. }
    7. var foo = new Foo();
    8. foo.l.push(foo.i);
    9. foo.l.push("end");

    函数参数是默认可选的

    由于在ES2015之前无法指定可选参数,因此.js文件里所有函数参数都被当做是可选的。使用比预期少的参数调用函数是允许的。

    需要注意的一点是,使用过多的参数调用函数会得到一个错误。

    例如:

    1. function bar(a, b) {
    2. console.log(a + " " + b);
    3. }
    4. bar(1); // OK, second argument considered optional
    5. bar(1, 2);
    6. bar(1, 2, 3); // Error, too many arguments

    使用JSDoc注解的函数会被从这条规则里移除。使用JSDoc可选参数语法来表示可选性。比如:

    1. /**
    2. * @param {string} [somebody] - Somebody's name.
    3. */
    4. function sayHello(somebody) {
    5. if (!somebody) {
    6. somebody = 'John Doe';
    7. }
    8. console.log('Hello ' + somebody);
    9. }
    10. sayHello();

    由arguments推断出的var-args参数声明

    如果一个函数的函数体内有对arguments的引用,那么这个函数会隐式地被认为具有一个var-arg参数(比如:(…arg: any[]) => any))。使用JSDoc的var-arg语法来指定arguments的类型。

    1. /** @param {...number} args */
    2. function sum(/* numbers */) {
    3. var total = 0
    4. for (var i = 0; i < arguments.length; i++) {
    5. total += arguments[i]
    6. }
    7. return total
    8. }

    未指定的类型参数默认为any

    由于JavaScript里没有一种自然的语法来指定泛型参数,因此未指定的参数类型默认为any

    在extends语句中:

    例如,React.Component被定义成具有两个类型参数,PropsState。在一个.js文件里,没有一个合法的方式在extends语句里指定它们。默认地参数类型为any

    1. import { Component } from "react";
    2. class MyComponent extends Component {
    3. render() {
    4. this.props.b; // Allowed, since this.props is of type any
    5. }
    6. }

    使用JSDoc的@augments来明确地指定类型。例如:

    1. import { Component } from "react";
    2. /**
    3. * @augments {Component<{a: number}, State>}
    4. */
    5. class MyComponent extends Component {
    6. render() {
    7. this.props.b; // Error: b does not exist on {a:number}
    8. }
    9. }

    在JSDoc引用中:

    JSDoc里未指定的类型参数默认为any

    1. /** @type{Array} */
    2. var x = [];
    3. x.push(1); // OK
    4. x.push("string"); // OK, x is of type Array<any>
    5. /** @type{Array.<number>} */
    6. var y = [];
    7. y.push(1); // OK
    8. y.push("string"); // Error, string is not assignable to number

    在函数调用中

    泛型函数的调用使用arguments来推断泛型参数。有时候,这个流程不能够推断出类型,大多是因为缺少推断的源;在这种情况下,类型参数类型默认为any。例如:

    1. var p = new Promise((resolve, reject) => { reject() });
    2. 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它们代表的意义与usejsdoc.org上面给出的通常是一致的或者是它的超集。下面的代码描述了它们的区别并给出了一些示例。

    @type

    可以使用@type标记并引用一个类型名称(原始类型,TypeScript里声明的类型,或在JSDoc里@typedef标记指定的)可以使用任何TypeScript类型和大多数JSDoc类型。

    1. /**
    2. * @type {string}
    3. */
    4. var s;
    5. /** @type {Window} */
    6. var win;
    7. /** @type {PromiseLike<string>} */
    8. var promisedString;
    9. // You can specify an HTML Element with DOM properties
    10. /** @type {HTMLElement} */
    11. var myElement = document.querySelector(selector);
    12. element.dataset.myData = '';

    @type可以指定联合类型—例如,stringboolean类型的联合。

    1. /**
    2. * @type {(string | boolean)}
    3. */
    4. var sb;

    注意,括号是可选的。

    1. /**
    2. * @type {string | boolean}
    3. */
    4. var sb;

    有多种方式来指定数组类型:

    1. /** @type {number[]} */
    2. var ns;
    3. /** @type {Array.<number>} */
    4. var nds;
    5. /** @type {Array<number>} */
    6. var nas;

    还可以指定对象字面量类型。例如,一个带有a(字符串)和b(数字)属性的对象,使用下面的语法:

    1. /** @type {{ a: string, b: number }} */
    2. var var9;

    可以使用字符串和数字索引签名来指定map-likearray-like的对象,使用标准的JSDoc语法或者TypeScript语法。

    1. /**
    2. * A map-like object that maps arbitrary `string` properties to `number`s.
    3. *
    4. * @type {Object.<string, number>}
    5. */
    6. var stringToNumber;
    7. /** @type {Object.<number, object>} */
    8. var arrayLike;

    这两个类型与TypeScript里的{ [x: string]: number }{ [x: number]: any }是等同的。编译器能识别出这两种语法。

    可以使用TypeScript或Closure语法指定函数类型。

    1. /** @type {function(string, boolean): number} Closure syntax */
    2. var sbn;
    3. /** @type {(s: string, b: boolean) => number} Typescript syntax */
    4. var sbn2;

    或者直接使用未指定的Function类型:

    1. /** @type {Function} */
    2. var fn7;
    3. /** @type {function} */
    4. var fn6;

    Closure的其它类型也可以使用:

    1. /**
    2. * @type {*} - can be 'any' type
    3. */
    4. var star;
    5. /**
    6. * @type {?} - unknown type (same as 'any')
    7. */
    8. var question;

    转换

    TypeScript借鉴了Closure里的转换语法。在括号表达式前面使用@type标记,可以将一种类型转换成另一种类型

    1. /**
    2. * @type {number | string}
    3. */
    4. var numberOrString = Math.random() < 0.5 ? "hello" : 100;
    5. var typeAssertedNumber = /** @type {number} */ (numberOrString)

    导入类型

    可以使用导入类型从其它文件中导入声明。这个语法是TypeScript特有的,与JSDoc标准不同:

    1. /**
    2. * @param p { import("./a").Pet }
    3. */
    4. function walk(p) {
    5. console.log(`Walking ${p.name}...`);
    6. }

    导入类型也可以使用在类型别名声明中:

    1. /**
    2. * @typedef Pet { import("./a").Pet }
    3. */
    4. /**
    5. * @type {Pet}
    6. */
    7. var myPet;
    8. myPet.name;

    导入类型可以用在从模块中得到一个值的类型。

    1. /**
    2. * @type {typeof import("./a").x }
    3. */
    4. var x = require("./a").x;

    @param和@returns

    @param语法和@type相同,但增加了一个参数名。使用[]可以把参数声明为可选的:

    1. // Parameters may be declared in a variety of syntactic forms
    2. /**
    3. * @param {string} p1 - A string param.
    4. * @param {string=} p2 - An optional param (Closure syntax)
    5. * @param {string} [p3] - Another optional param (JSDoc syntax).
    6. * @param {string} [p4="test"] - An optional param with a default value
    7. * @return {string} This is the result
    8. */
    9. function stringsStringStrings(p1, p2, p3, p4){
    10. // TODO
    11. }

    函数的返回值类型也是类似的:

    1. /**
    2. * @return {PromiseLike<string>}
    3. */
    4. function ps(){}
    5. /**
    6. * @returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
    7. */
    8. function ab(){}

    @typedef, @callback, 和 @param

    @typedef可以用来声明复杂类型。和@param类似的语法。

    1. /**
    2. * @typedef {Object} SpecialType - creates a new type named 'SpecialType'
    3. * @property {string} prop1 - a string property of SpecialType
    4. * @property {number} prop2 - a number property of SpecialType
    5. * @property {number=} prop3 - an optional number property of SpecialType
    6. * @prop {number} [prop4] - an optional number property of SpecialType
    7. * @prop {number} [prop5=42] - an optional number property of SpecialType with default
    8. */
    9. /** @type {SpecialType} */
    10. var specialTypeObject;

    可以在第一行上使用objectObject

    1. /**
    2. * @typedef {object} SpecialType1 - creates a new type named 'SpecialType'
    3. * @property {string} prop1 - a string property of SpecialType
    4. * @property {number} prop2 - a number property of SpecialType
    5. * @property {number=} prop3 - an optional number property of SpecialType
    6. */
    7. /** @type {SpecialType1} */
    8. var specialTypeObject1;

    @param允许使用相似的语法。注意,嵌套的属性名必须使用参数名做为前缀:

    1. /**
    2. * @param {Object} options - The shape is the same as SpecialType above
    3. * @param {string} options.prop1
    4. * @param {number} options.prop2
    5. * @param {number=} options.prop3
    6. * @param {number} [options.prop4]
    7. * @param {number} [options.prop5=42]
    8. */
    9. function special(options) {
    10. return (options.prop4 || 1001) + options.prop5;
    11. }

    @callback@typedef相似,但它指定函数类型而不是对象类型:

    1. /**
    2. * @callback Predicate
    3. * @param {string} data
    4. * @param {number} [index]
    5. * @returns {boolean}
    6. */
    7. /** @type {Predicate} */
    8. const ok = s => !(s.length % 2);

    当然,所有这些类型都可以使用TypeScript的语法@typedef在一行上声明:

    1. /** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
    2. /** @typedef {(data: string, index?: number) => boolean} Predicate */

    @template

    使用@template声明泛型:

    1. /**
    2. * @template T
    3. * @param {T} p1 - A generic parameter that flows through to the return type
    4. * @return {T}
    5. */
    6. function id(x){ return x }

    用逗号或多个标记来声明多个类型参数:

    1. /**
    2. * @template T,U,V
    3. * @template W,X
    4. */

    还可以在参数名前指定类型约束。只有列表的第一项类型参数会被约束:

    1. /**
    2. * @template {string} K - K must be a string or string literal
    3. * @template {{ serious(): string }} Seriousalizable - must have a serious method
    4. * @param {K} key
    5. * @param {Seriousalizable} object
    6. */
    7. function seriousalize(key, object) {
    8. // ????
    9. }

    @constructor

    编译器通过this属性的赋值来推断构造函数,但你可以让检查更严格提示更友好,你可以添加一个@constructor标记:

    1. /**
    2. * @constructor
    3. * @param {number} data
    4. */
    5. function C(data) {
    6. this.size = 0;
    7. this.initialize(data); // Should error, initializer expects a string
    8. }
    9. /**
    10. * @param {string} s
    11. */
    12. C.prototype.initialize = function (s) {
    13. this.size = s.length
    14. }
    15. var c = new C(0);
    16. var result = C(1); // C should only be called with new

    通过@constructorthis将在构造函数C里被检查,因此你在initialize方法里得到一个提示,如果你传入一个数字你还将得到一个错误提示。如果你直接调用C而不是构造它,也会得到一个错误。

    不幸的是,这意味着那些既能构造也能直接调用的构造函数不能使用@constructor

    @this

    编译器通常可以通过上下文来推断出this的类型。但你可以使用@this来明确指定它的类型:

    1. /**
    2. * @this {HTMLElement}
    3. * @param {*} e
    4. */
    5. function callbackForLater(e) {
    6. this.clientHeight = parseInt(e) // should be fine!
    7. }

    @extends

    当JavaScript类继承了一个基类,无处指定类型参数的类型。而@extends标记提供了这样一种方式:

    1. /**
    2. * @template T
    3. * @extends {Set<T>}
    4. */
    5. class SortableSet extends Set {
    6. // ...
    7. }

    注意@extends只作用于类。当前,无法实现构造函数继承类的情况。

    @enum

    @enum标记允许你创建一个对象字面量,它的成员都有确定的类型。不同于JavaScript里大多数的对象字面量,它不允许添加额外成员。

    1. /** @enum {number} */
    2. const JSDocState = {
    3. BeginningOfLine: 0,
    4. SawAsterisk: 1,
    5. SavingComments: 2,
    6. }

    注意@enum与TypeScript的@enum大不相同,它更加简单。然而,不同于TypeScript的枚举,@enum可以是任何类型:

    1. /** @enum {function(number): number} */
    2. const Math = {
    3. add1: n => n + 1,
    4. id: n => -n,
    5. sub1: n => n - 1,
    6. }

    更多示例

    1. var someObj = {
    2. /**
    3. * @param {string} param1 - Docs on property assignments work
    4. */
    5. x: function(param1){}
    6. };
    7. /**
    8. * As do docs on variable assignments
    9. * @return {Window}
    10. */
    11. let someFunc = function(){};
    12. /**
    13. * And class methods
    14. * @param {string} greeting The greeting to use
    15. */
    16. Foo.prototype.sayHi = (greeting) => console.log("Hi!");
    17. /**
    18. * And arrow functions expressions
    19. * @param {number} x - A multiplier
    20. */
    21. let myArrow = x => x * x;
    22. /**
    23. * Which means it works for stateless function components in JSX too
    24. * @param {{a: string, b: number}} test - Some param
    25. */
    26. var sfc = (test) => <div>{test.a.charAt(0)}</div>;
    27. /**
    28. * A parameter can be a class constructor, using Closure syntax.
    29. *
    30. * @param {{new(...args: any[]): object}} C - The class to register
    31. */
    32. function registerClass(C) {}
    33. /**
    34. * @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
    35. */
    36. function fn10(p1){}
    37. /**
    38. * @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
    39. */
    40. function fn9(p1) {
    41. return p1.join();
    42. }

    已知不支持的模式

    在值空间中将对象视为类型是不可以的,除非对象创建了类型,如构造函数。

    1. function aNormalFunction() {
    2. }
    3. /**
    4. * @type {aNormalFunction}
    5. */
    6. var wrong;
    7. /**
    8. * Use 'typeof' instead:
    9. * @type {typeof aNormalFunction}
    10. */
    11. var right;

    对象字面量属性上的=后缀不能指定这个属性是可选的:

    1. /**
    2. * @type {{ a: string, b: number= }}
    3. */
    4. var wrong;
    5. /**
    6. * Use postfix question on the property name instead:
    7. * @type {{ a: string, b?: number }}
    8. */
    9. var right;

    Nullable类型只在启用了strictNullChecks检查时才启作用:

    1. /**
    2. * @type {?number}
    3. * With strictNullChecks: true -- number | null
    4. * With strictNullChecks: off -- number
    5. */
    6. var nullable;

    Non-nullable类型没有意义,以其原类型对待:

    1. /**
    2. * @type {!number}
    3. * Just has type number
    4. */
    5. var normal;

    不同于JSDoc类型系统,TypeScript只允许将类型标记为包不包含null。没有明确的Non-nullable — 如果启用了strictNullChecks,那么number是非null的。如果没有启用,那么number是可以为null的。