一、引言
昨天的文章七天学TypeScript(二) 主要和崔老师学习了类型收窄,今天学习函数。接触的高级语言不少,但是JavaScript这门既简单又复杂的编程语言,决定了TypeScript不会那么简单。函数就有很多零散的点,今天我们重点抓范型和重载,范型相对实用且容易上手。
二、范型
范型简单说就是类型变量,golang到现在一直在纠结要不要增加范型支持。范型可以说是介于固定类型和any类型之间,使用起来灵活有度,换句话说保持了javascript的“灵活”和静态类型的高级语言语言的“有度”。 TypeScript有范型支持也是要感谢大佬安德斯·海尔斯伯格(Anders Hejlsberg) ,不愧是TypeScript、C#、Delphi(甚至其前身Pascal)的架构师,20多年编程语言的设计经验不是盖的。
1、先看一个简单例子。
返回数组第一个元素,注意如果传空数组会返回undefined,语义非常明确,代码也简洁。
-
代码:
function firstElement<Type>(arr: Type[]): Type | undefined { return arr[0]; } //调用例子 // 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([]);
2、稍微复杂一点儿的例子,有多个范型参数。
-
代码:
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] { return arr.map(func); } // Parameter 'n' is of type 'string' // 返回number[] const parsed = map(["1", "2", "3"], (n) => parseInt(n));
3、下面一个例子Type参数是有length属性的类型,充分体现使用范型的灵活度
-
代码:
function longest<Type extends { length: number }>(a: Type, b: Type) { if (a.length >= b.length) { return a; } else { return b; } } //返回[1,2,3] const longerArray = longest([1, 2], [1, 2, 3]); //返回alice const longerString = longest("alice", "bob"); //传的number没有length属性,报错 const notOK = longest(10, 100);
4、好的范型例子,体会一下。
-
代码:
function firstElement1<Type>(arr: Type[]) { return arr[0]; } function firstElement2<Type extends any[]>(arr: Type) { return arr[0]; } // a: number (good) const a = firstElement1([1, 2, 3]); // b: any (bad) const b = firstElement2([1, 2, 3]);
5、下面的函数两种写法都可以,但是对比一下,可以看到第二种写法更晦涩难懂,不推荐。
-
代码:
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] { return arr.filter(func); } function filter2<Type, Func extends (arg: Type) => boolean>( arr: Type[], func: Func ): Type[] { return arr.filter(func); }
6、不要过度使用范型,这里面都是哲学。
-
代码
function greet<Str extends string>(s: Str) { console.log("Hello, " + s); } greet("world"); //上面的函数为了用范型而用范型,本来就可以用string代替,像下面这样: function greet(s: string) { console.log("Hello, " + s); }
三、函数的几个其它知识点
函数里面还有一些基础的语法,别的开发语言一般都有,虽然不一定经常用到,记下来备查吧。
1、可选参数,C语言里有我记得是...,另外文档里说了回调函数的参数不要定义为可选参数。
-
例子:
function f(x?: number) { // ... } f(); // OK f(10); // OK //也OK f(undefined);
2、参数默认值,C语言也有
-
例子:
function f(x = 10) { // ... }
3、this使用
-
例子
const user = { id: 123, admin: false, becomeAdmin: function () { this.admin = true; }, }; //
4、涉及到几个类型,void,object,unknown,any,never,今天略过,以后再补。
5、全局类型Function
-
例子
function doSomething(f: Function) { f(1, 2, 3); }
6、Rest形参和实参
-
例子
//形参Parameters function multiply(n: number, ...m: number[]) { return m.map((x) => n * x); } // 'a' gets value [10, 20, 30, 40] const a = multiply(10, 1, 2, 3, 4); //实参Arguments const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; arr1.push(...arr2);
7、参数解构
-
例子:
//类似JS写法可以 function sum({ a, b, c }) { console.log(a + b + c); } sum({ a: 10, b: 3, c: 9 }); //标注类型,更明确一些 function sum({ a, b, c }: { a: number; b: number; c: number }) { console.log(a + b + c); } //上面看上去参数有点儿冗长,可用下面这种具名方式替代 type ABC = { a: number; b: number; c: number }; function sum({ a, b, c }: ABC) { console.log(a + b + c); }
8、函数Assignability(可以翻译成转赋值或转让)
-
例子
type voidFunc = () => void; const f1: voidFunc = () => { return true; }; const f2: voidFunc = () => true; const f3: voidFunc = function () { return true; }; //经过函数转让,void返回值是被覆盖了 const v1 = f1(); const v2 = f2(); const v3 = f3(); //正如forEach接受的参数是返回void的函数,但是这里push是返回number,稍微有点儿晦涩。 const src = [1, 2, 3]; const dst = [0]; src.forEach((el) => dst.push(el));
四、函数重载
1、函数重载,同名参数不同(类型或个数)。
-
代码:
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); //会报错,因为函数声明里要么是一个参数number,要么是3个参数年(number),月(number),日(number) const d3 = makeDate(1, 3);
2、重载写法有问题的几个例子,原因是实现不能涵盖声明的各种参数。
-
例子:
//错误1 function fn(x: string): void; function fn() { // ... } // Expected to be able to call with zero arguments fn(); Expected 1 arguments, but got 0. //错误2 function fn(x: boolean): void; // Argument type isn't right function fn(x: string): void; This overload signature is not compatible with its implementation signature. function fn(x: boolean) {} //错误3 实现的返回值和声明不兼容,实现返回的是字符串,而声明返回有string和boolean两种 function fn(x: string): string; // Return type isn't right function fn(x: number): boolean; This overload signature is not compatible with its implementation signature. function fn(x: string | number) { return "oops"; } //错误4 声明没毛病 function len(s: string): number; function len(arr: any[]): number; function len(x: any) { return x.length; } len(""); // OK len([0]); // OK //调用有问题 'number[] | "hello"' 既和string不兼容又和number[]不兼容,注意体会 len(Math.random() > 0.5 ? "hello" : [0]);
3、上面的错误4改成下面的union当参数更好,简洁且舒服。和范型一样,不要为了用重载而用重载,很哲学。
-
例子:
function len(x: any[] | string) { return x.length; }
在数据结构和算法里,范型和重载应用还是比较普遍的。明天重点学习“对象”,“对象”是程序员穷其一生要反复揣摩和面对的,研究好“面向对象”,你就会有一个“好对象”,加油!待续。