七天学习TypeScript(三)

186 阅读5分钟

一、引言

  昨天的文章七天学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;
    }
    

  在数据结构和算法里,范型和重载应用还是比较普遍的。明天重点学习“对象”,“对象”是程序员穷其一生要反复揣摩和面对的,研究好“面向对象”,你就会有一个“好对象”,加油!待续。

  参考网址:www.typescriptlang.org