TypeScript$Type-Value-Function
P. Prepare
P.1 Function Signature
MDN 中关于函数签名的描述:
A function signature (or type signature, or method signature) defines input and output of functions or methods.
A signature can include:
- parameters and their types
- a return value and type
- exceptions that might be thrown or passed back
- information about the availability of the method in an object-oriented program (such as the keywords
public,static, orprototype).
函数签名用来描述函数的信息,包括输入输出及函数自身的信息。其中输入包括参数和类型,输出包括返回值和类型以及异常信息,函数自身的信息包括函数名、 public, static, or prototype 等。
在 Java 中,函数定义的第一行就包括了函数签名的所有信息;在 JavaScript 中,函数定义的第一行只能表示函数签名的一部分:
- 输入不涉及类型,只有 parameters。(因为 JavaScript 是动态类型语言,值有类型但变量没有)
- 输出:第一行没有返回值,在函数体中可以看到返回值,但不涉及类型。
- 函数自身的信息:函数名、
static等信息。如果在 class 内,我们知道这是prototype;如果有#,我们知道这是私有方法。
TypeScript 的存在就是为了给 JavaScript 加类型,所以在 TypeScript 中,需要声明输入和输出的类型。
JavaScript 中,只要函数名相同(不在乎输入输出),都会被认为是同一个函数(后来的函数会覆盖之前的函数)。加了类型后,就需要表示这些输入输出的不同情况,所以 TypeScript 中会有 Function Overloads 来表示这些信息。
P.2 First-class Function
A programming language is said to have First-class functions when functions in that language are treated like any other variable.
JavaScript 中函数被称为 first-class,因为函数就像其他变量一样。因为函数是变量,所以函数除了可以被执行,还可以有自己的属性。TypeScript 中,Call Signature 就是用来表示有属性的函数。
P.3 Class is a Function
在 JavaScript 中,class 其实是函数的语法糖。class 的存在简化了类的声明,但是本质上 class 还是函数。函数可以通过 new 生成实例。TypeScript 中的 construct signature 用来描述用来 new 的函数。
0. Function Type
function doSomething(f: Function) {
return f(1, 2, 3);
}
1. Function Type Expressions
// (a: string) => void
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
// inteface
interface voidFunction {
(a: string): void;
}
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
2. Call Signatures
如果函数带有属性,需要使用 call signatures 来表示
type DescribableFunction = {
description: string;
(someArg: number): boolean; // not =>
};
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
function myFunc(someArg: number) {
return someArg > 3;
}
myFunc.description = "default description";
doSomething(myFunc);
3. Construct Signatures
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
interface CallOrConstruct {
(n?: number): string;
new (s: string): Date;
}
4. Generic Functions
如果函数的类型之间有关系,可以使用泛型。泛型是一个类型的占位符 type parameter,可以通过这个占位符表示类型的关系。在具体使用的时候这个占位符得到了初始化。
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
// infer: s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);
Constraints
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
// error, Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
/* error, Type '{ length: number; }' is not assignable to type 'Type'.
'{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
}
} */
Specifying Type Arguments
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
const arr = combine([1, 2, 3], ["hello"]);
// error, Type 'string' is not assignable to type 'number'.
const arr = combine<string | number>([1, 2, 3], ["hello"]);
5. Function Overloads
Some number of function signatures (usually two or more) (overload signatures), followed by the body of the function (implementation signature).
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
// error, No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
Always prefer parameters with union types instead of overloads when possible.
6. Declaring this in a Function
The JavaScript specification states that you cannot have a parameter called this, and so TypeScript uses that syntax space to let you declare the type for this in the function body.
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});