1. JSDoc and Type Checking
.js files don't support TypeScript type annotation syntax:
// Error 'types' can only be used in a .ts file.
let x: number;
Therefore, for .js files, need a type annotation method compatible with JavaScript syntax, such as JSDoc:
/** @type {number} */
let x;
// Error Type '"string"' is not assignable to type 'number'.
x = 'string';
Express types through this special form (starting with /**) of comments, thereby compatible with JavaScript syntax. TypeScript type system parses these JSDoc tags to get extra type information input, and combines with type inference to perform type checking on .js files
P.S. For more information about .js type checking, see [Checking JavaScript Files_TypeScript Notes 18](/articles/检查 javascript 文件-typescript 笔记 18/)
2. Support Level
TypeScript currently (2019/5/12) only supports partial JSDoc tags, specifically:
-
@type: Describe objects -
@param(or@argor@argument): Describe function parameters -
@returns(or@return): Describe function return values -
@typedef: Describe custom types -
@callback: Describe callback functions -
@class(or@constructor): Indicate this function should be called withnewkeyword -
@this: Describethispointing here -
@extends(or@augments): Describe inheritance relationships -
@enum: Describe a set of related properties -
@property(or@prop): Describe object properties
P.S. Complete JSDoc tag list see Block Tags
Specially, for generics, JSDoc doesn't provide suitable tags, therefore extended extra tags:
@template: Describe generics
P.S. Using @template tag to describe generics originates from Google Closure Compiler, more related discussions see Add support for @template JSDoc
3. Type Annotation Syntax
TypeScript is compatible with JSDoc type annotations, also supports using TypeScript type annotation syntax in JSDoc tags:
The meaning is usually the same, or a superset, of the meaning of the tag given at usejsdoc.org.
Yes, superset again, therefore any type has 3 annotation methods:
// JSDoc type annotation syntax
/** @type {*} - can be 'any' type */
var star = true;
/** @type {?} - unknown type (same as 'any') */
var question = true;
// Both equivalent to TypeScript type annotation syntax
/** @type {any} */
var thing = true;
Syntax-wise, JSDoc mostly borrows from Google Closure Compiler type annotations, while TypeScript has its own set of [type syntax](/articles/基本类型-typescript 笔记 2/), therefore二者 exist some differences
Type Declarations
Use @typedef tag to declare custom types, for example:
/**
* @typedef {Object} SpecialType - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
* @prop {number} [prop4] - an optional number property of SpecialType
* @prop {number} [prop5=42] - an optional number property of SpecialType with default
*/
/** @type {SpecialType} */
var specialTypeObject;
Equivalent to following TypeScript code:
type SpecialType = {
prop1: string;
prop2: number;
prop3?: number;
prop4?: number;
prop5?: number;
}
let specialTypeObject: SpecialType;
Type References
Reference type names through @type tag, type names can be basic types, or types defined in TypeScript declaration files (d.ts) or defined through JSDoc tag @typedef
For example:
// Basic types
/**
* @type {string}
*/
var s;
/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;
/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;
// Types defined in external declaration files
/** @type {Window} */
var win;
/** @type {PromiseLike<string>} */
var promisedString;
/** @type {HTMLElement} */
var myElement = document.querySelector('#root');
element.dataset.myData = '';
// Types defined by JSDoc @typedef
/** @typedef {(data: string, index?: number) => boolean} Predicate */
/** @type Predicate */
var p;
p('True or not ?');
Object types also described through object literals, index signatures also applicable:
/** @type {{ a: string, b: number }} */
var obj;
obj.a.toLowerCase();
/**
* String index signature
* @type {Object.<string, number>}
*/
var stringToNumber;
// Equivalent to
/** @type {{ [x: string]: number; }} */
var stringToNumber;
// Numeric index signature
/** @type {Object.<number, object>} */
var arrayLike;
// Equivalent to
/** @type {{ [x: number]: any; }} */
var arrayLike;
Function types also have two syntax options:
/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;
Former can omit parameter names, latter can omit function keyword, meanings are same
Also supports type combinations:
// Union types (JSDoc type syntax)
/**
* @type {(string | boolean)}
*/
var sb;
// Union types (TypeScript type syntax)
/**
* @type {string | boolean}
*/
var sb;
Both equivalent, just syntax slightly different
Cross-File Type References
Specially, can reference types defined in other files through import:
// a.js
/**
* @typedef Pet
* @property name {string}
*/
module.exports = {/* ... */};
// index.js
// 1.Reference type
/**
* @param p { import("./a").Pet }
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
// 1.Reference type, simultaneously create alias
/**
* @typedef { import("./a").Pet } Pet
*/
/**
* @type {Pet}
*/
var myPet;
myPet.name;
// 3.Reference inferred types
/**
* @type {typeof import("./a").x }
*/
var x = require("./a").x;
Note, this syntax is TypeScript-specific (JSDoc doesn't support), while JSDoc uses ES Module import syntax:
// a.js
/**
* @typedef State
* @property {Array} layers
* @property {object} product
*/
// index.js
import * as A from './a';
/** @param {A.State} state */
const f = state => ({
product: state.product,
layers: state.layers,
});
This method will add actual import, if it's a pure type declaration file (.js only containing @typedef, similar to d.ts), JSDoc method will import a useless file (only containing comments), while TypeScript method doesn't have this problem
P.S. TypeScript is compatible with both type import syntaxes, more related discussions see Question: Import typedef from another file?
Type Conversion
Type conversion (TypeScript's [type assertions](/articles/基本类型-typescript 笔记 2/#articleHeader4)) syntax is consistent with JSDoc, through @type tag before parentheses to explain type of expression in parentheses:
/** @type {!MyType} */ (valueExpression)
For example:
/** @type {number | string} */
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)
// Error Type '"hello"' is not assignable to type 'number'.
typeAssertedNumber = 'hello';
P.S. Note, must have parentheses, otherwise not recognized
4. Common Types
Objects
Generally use @typedef tag to describe object types, for example:
/**
* The complete Triforce, or one or more components of the Triforce.
* @typedef {Object} WishGranter~Triforce
* @property {boolean} hasCourage - Indicates whether the Courage component is present.
* @property {boolean} hasPower - Indicates whether the Power component is present.
* @property {boolean} hasWisdom - Indicates whether the Wisdom component is present.
*/
Equivalent to TypeScript types:
interface WishGranter {
hasCourage: boolean;
hasPower: boolean;
hasWisdom: boolean;
}
// Or
type WishGranter = {
hasCourage: boolean;
hasPower: boolean;
hasWisdom: boolean;
}
If just one-time type declaration (no need to reuse, don't want to additionally define types), can use @param tag to declare, describe member property nesting relationships through options.prop1 form of property names:
/**
* @param {Object} options - The shape is the same as SpecialType above
* @param {string} options.prop1
* @param {number} options.prop2
* @param {number=} options.prop3
* @param {number} [options.prop4]
* @param {number} [options.prop5=42]
*/
function special(options) {
return (options.prop4 || 1001) + options.prop5;
}
Functions
Similar to using @typedef tag to describe objects, can use @callback tag to describe function types:
/**
* @callback Predicate
* @param {string} data
* @param {number} [index]
* @returns {boolean}
*/
/** @type {Predicate} */
const ok = s => !(s.length % 2);
Equivalent to TypeScript code:
type Predicate = (data: string, index?: number) => boolean
Can also use @typedef special syntax (only TypeScript supports, doesn't exist in JSDoc) to integrate object or function type definitions into one line:
/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */
// Equivalent to TypeScript code
type SpecialType = {
prop1: string;
prop2: string;
prop3?: number;
}
type Predicate = (data: string, index?: number) => boolean
Parameters
Function parameters described through @param tag, same syntax as @type, just adds a parameter name, for example:
/**
* @param {string} p1 A required parameter
*/
function f(p1) {}
Optional parameters have 3 representation methods:
/**
* @param {string=} p1 - Optional parameter (Closure syntax)
* @param {string} [p2] - Optional parameter (JSDoc syntax)
* @param {string} [p3 = 'test'] - Optional parameter with default value (JSDoc syntax)
*/
function fn(p1, p2, p3) {}
P.S. Note, suffix equals syntax (such as {string=}) doesn't apply to object literal types, for example @type {{ a: string, b: number= }} is illegal type declaration, optional properties should use property name suffix ? to express
Rest parameters have 2 representation methods:
/**
* @param {...string} p - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn(p){ arguments; }
/** @type {(...args: any[]) => void} */
function f() { arguments; }
Return Values
Return value type annotation methods also similar:
/**
* @return {PromiseLike<string>}
*/
function ps() {
return Promise.resolve('');
}
/**
* @returns {{ a: string, b: number }}
*/
function ab() {
return {a: 'a', b: 11};
}
P.S. @returns and @return are completely equivalent, latter is alias of former
Classes
Constructors
Type system infers constructors based on attribute assignments to this, can also describe constructors through @constructor tag
Difference between二者 is when there's @constructor tag, type checking is stricter. Specifically, will check this property access in constructor and constructor parameters, and doesn't allow directly calling constructor (not through new keyword):
/**
* @constructor
* @param {number} data
*/
function C(data) {
this.size = 0;
// Error Argument of type 'number' is not assignable to parameter of type 'string'.
this.initialize(data);
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length
}
var c = new C(0);
// Error Value of type 'typeof C' is not callable. Did you mean to include 'new'?
var result = C(1);
P.S. If remove @constructor tag, won't report these two errors
Additionally, for constructor or class type parameters, can describe their types through TypeScript-like syntax:
/**
* @template T
* @param {{new(): T}} C Requires constructor C must return instances of same class (or subclass)
* @returns {T}
*/
function create(C) {
return new C();
}
P.S. JSDoc doesn't provide way to describe Newable parameters, specifically see Document class types/constructor types
this Types
Most of the time type system can infer this type based on context, for complex scenarios can explicitly specify this type through @this tag:
// Inferred type is function getNodeHieght(): any
function getNodeHieght() {
return this.innerHeight;
}
// Explicitly specify this type, inferred type is function getNodeHieght(): number
/**
* @this {HTMLElement}
*/
function getNodeHieght() {
return this.clientHeight;
}
Inheritance
In TypeScript, class inheritance relationships cannot be described through JSDoc:
class Animal {
alive = true;
move() {}
}
/**
* @extends {Animal}
*/
class Duck {}
// Error Property 'move' does not exist on type 'Duck'.
new Duck().move();
@augments (or @extends) only used to specify base class generic parameters:
/**
* @template T
*/
class Box {
/**
* @param {T} value
*/
constructor(value) {
this.value = value;
}
unwrap() {
return this.value;
}
}
/**
* @augments {Box<string>} Description
*/
class StringBox extends Box {
constructor() {
super('string');
}
}
new StringBox().unwrap().toUpperCase();
But different from JSDoc, @arguments/extends tags can only be used for Class, constructors not applicable:
/**
* @constructor
*/
function Animal() {
this.alive = true;
}
/**
* @constructor
* @augments Animal
*/
// Error JSDoc '@augments' is not attached to a class.
function Duck() {}
Duck.prototype = new Animal();
Therefore, @augments/extends tag's effect is very weak, neither can describe non-Class inheritance, nor can determine inheritance relationships (inheritance relationships determined by extends clause, JSDoc descriptions don't count)
Enums
Enums described with @enum tag, but different from TypeScript enum types, main differences are:
-
Require enum member types to be consistent
-
But enum members can be any type
For example:
/** @enum {number} */
const JSDocState = {
BeginningOfLine: 0,
SawAsterisk: 1,
SavingComments: 2,
}
/** @enum {function(number): number} */
const SimpleMath = {
add1: n => n + 1,
id: n => n,
sub1: n => n - 1,
}
Generics
Generics described with @template tag:
/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @return {T}
*/
function id(x) { return x }
let x = id('string');
// Error Type '0' is not assignable to type 'string'.
x = 0;
Equivalent to TypeScript code:
function id<T>(x: T): T {
return x;
}
let x = id('string');
x = 0;
When there are multiple [type parameters](/articles/泛型-typescript 笔记 6/#articleHeader3), can separate with commas, or use multiple @template tags:
/**
* @template T, U
* @param {[T, U]} pairs Tuple
* @returns {[U, T]}
*/
function reversePairs(pairs) {
const x = pairs[0];
const y = pairs[1];
return [y, x];
}
// Equivalent to
/**
* @template T
* @template U
* @param {[T, U]} pairs Tuple
* @returns {[U, T]}
*/
function reversePairs(pairs) {
const x = pairs[0];
const y = pairs[1];
return [y, x];
}
Additionally, also supports [generic constraints](/articles/泛型-typescript 笔记 6/#articleHeader8):
/**
* @typedef Lengthwise
* @property length {number}
*/
/**
* @template {Lengthwise} T
* @param {T} arg
* @returns {T}
*/
function loggingIdentity(arg) {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
Equivalent to TypeScript code:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
Specially, when combining @typedef tag to define generic types, must define generic parameters first:
/**
* @template K
* @typedef Wrapper
* @property value {K}
*/
/** @type {Wrapper<string>} */
var s;
s.value.toLocaleLowerCase();
@template and @typedef order cannot be reversed, otherwise error:
JSDoc '@typedef' tag should either have a type annotation or be followed by '@property' or '@member' tags.
Equivalent to TypeScript generic declaration:
type Wrapper<K> = {
value: K;
}
Nullable
In JSDoc, can explicitly specify Nullable types and Non-Null types, for example:
-
{?number}: Representsnumber | null -
{!number}: Representsnumber
While in TypeScript cannot explicitly specify, whether type contains Null only relates to --strictNullChecks option:
/**
* @type {?number}
* When strictNullChecks is enabled, type is number | null
* When strictNullChecks is disabled, type is number
*/
var nullable;
/**
* @type {!number} Explicitly specifying non-Null is invalid, only relates to strictNullChecks option
*/
var normal;
No comments yet. Be the first to share your thoughts.