TypeScript$Type-Value-Function

91 阅读4分钟

TypeScript$Type-Value-Function

P. Prepare

P.1 Function Signature

MDNFunctionSignature

MDN 中关于函数签名的描述:

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 publicstatic, or prototype).

函数签名用来描述函数的信息,包括输入输出及函数自身的信息。其中输入包括参数和类型,输出包括返回值和类型以及异常信息,函数自身的信息包括函数名、 public, static, or prototype 等。

在 Java 中,函数定义的第一行就包括了函数签名的所有信息;在 JavaScript 中,函数定义的第一行只能表示函数签名的一部分:

  • 输入不涉及类型,只有 parameters。(因为 JavaScript 是动态类型语言,值有类型但变量没有)
  • 输出:第一行没有返回值,在函数体中可以看到返回值,但不涉及类型。
  • 函数自身的信息:函数名、static 等信息。如果在 class 内,我们知道这是 prototype;如果有 #,我们知道这是私有方法。

TypeScript 的存在就是为了给 JavaScript 加类型,所以在 TypeScript 中,需要声明输入和输出的类型。

JavaScript 中,只要函数名相同(不在乎输入输出),都会被认为是同一个函数(后来的函数会覆盖之前的函数)。加了类型后,就需要表示这些输入输出的不同情况,所以 TypeScript 中会有 Function Overloads 来表示这些信息。

P.2 First-class Function

MDNFirstClassFunction

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;
});

Links

TypeScriptFunctions