前端面试要点(二):Typescript

221 阅读5分钟

前言

在面试和复习过程中总结的一些前端知识点,记录下来,风格较简洁,尽量涵盖内容要点和简单例子。本文是前端面试系列第二篇:TypeScript 相关要点,持续更新...

一、Typescript 的概念

TypeScript(简称 TS) 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。TypeScript 通过 TypeScript编译器Babel 转译为 JavaScript 代码,可运行在任何浏览器,任何操作系统。

TS 的优势

  1. 可识别类型错误、拼写错误;
  2. 可自动联想;
  3. 类型可充当文档,提高代码可读性;

二、TS 中的一些对比

1. const vs readonly

  • const 表示声明了一个常量,值不可变;
  • readonly 用于 class 中,表示声明了一个只读属性,值不可变

2. interface vs type

相同点:

  • 都可以定义:对象、函数
  • 都支持继承

不同点:

  • interface 可以 重复声明,并把多次声明的结果合并
    interface i1 { a: Number }
    interface i1 { b: String }
    // 实际上 i1 为 { a: Number; b: String }
    
  • type 可以定义 基本类型,如 Number、String、Boolean
  • type 可以通过 typeof 操作符来定义,如 type t1 = typeof obj1
  • type 可以申明 联合类型,如 type t1 = t2 | t3
  • type 可以申明 元组类型,如 type t1 = [t2, t3]

3. extends vs implements

  • extents 是子类继承父类的所有属性和方法
  • implements 是一个类对一个接口的实现,它必须满足接口的所有规范
interface Person { money: Number }

class Father implements Person {
    public money: Number = 100
}

class Son extends Father {
    constructor() {
        super();
    }
    getMoney(): Number {
        return this.money;
    }
}

4. declare vs declare global

  • declare 在声明文件中定义全局类型,这样其它模块代码都可以用到该类型
    // global.d.ts
    declare var n: number;
    declare let s: string;
    
  • declare global 用于修改 已存在 的全局变量的声明
    // 给全局 String 类型增加 hump 方法
    declare global {
        interface String { hump(input: string): string; }
    }
    // 注意: 修改全局声明必须在模块内部, 所以至少要有 export{} 字样
    export {}
    

5. anyunknownvoidnever

  • any 可以跳过类型检查,任何类型都可以赋值给 any,反之也成立,这样就失去了类型检查的意义,慎用!
  • unknown 是所有类型的父类,任何类型都可以赋值给 unknown,但反之不成立,unknown 在赋值给其它变量前必须要先判断自己是什么类型才能继续使用。通常的使用场景是如果暂时不知道变量是什么类型可以先声明为 unknown,然后再根据后面的判断把它转化为想要的类型。
  • void 声明一个变量,则它只能被赋值 undefinednull
  • never 和 void 的混淆点在于它俩用于函数时都表示没有返回值,区别在于:
    • void 表示函数是正常运行的,本身设计就不存在返回值;
    • never 表示函数出现异常,比如抛出错误或死循环,存在无法到达的终点;
    function func1(): void { console.log(111) }
    function func2(): never { while (true) { } }
    

6. ?.!??_**

  • ?. 当左侧出现 undefined 或 null 时会自动停止运行表达式
    const a = b?.c // 当b为空值时,该表达式也不会报错
    
  • ! 非空断言运算符,将从自身类型中排除 null 和 undefined
    const a: { x?: number } = { x: 1 };
    // 报错:Type 'number | undefined' is not assignable to type 'number'
    const b: number = a.x; 
    // 不会报错,当明确 a.x 在这里实际有值时可以加 !
    const c: number = a.x!;
    
  • ?? 空值合并运算符,它和 || 的区别是:
    a ?? b // 当 a 是 undefined 或 null 时,表达式结果为 b,否则为 a
    a || b // 当 a 转化成 bool 值为 false 时,表达式结果为 b,否则为 a
    
  • _ 数字分隔符不会改变数字字面量的值,使人更容易读懂数字
    1_111_111 // 实际上就是 1111111,数字分隔符可以增加可读性
    
  • ** 幂运算
    3**2 // 结果为 9
    

6. ExcludeOmit

  • Exclude<T, U> 从类型集 T 中剔除类型集 U
    type T0 = Exclude<"a" | "b" | "c", "a">; // T0 = "b" | "c"
    
  • Omit<T, K> 从 T 的所有属性中剔除 K 属性
    interface Todo {
        title: string;
        description: string;
    }
    type TodoPreview = Omit<Todo, "description">;
    // TodoPreview = { title: string }
    

三、interface

1. interface 描述函数类型

interface ICompare {
    (a: number, b: number): boolean
}

const compareFunc: ICompare = (a, b) => a > b;

2. interface 描述可索引类型

可索引类型通常指 数组对象

interface IArray {
  [index: number]: string;
}

let myArray: IArray = ["Bob", "Fred"];

interface IObject {
    [key: string]: string;
}

let myObject: IObject = { a: '11', b: '22' };

四、联合类型

  • 联合类型包含了变量可能的所有类型,例:let res: number | string;
  • 对联合类型变量赋值为联合类型之外的值,会报错;
  • 在不能确定联合类型变量的最终类型之前,只能访问联合类型所共有的属性和方法,如上述例子中如果调用 res.length 就会报错,但如果类型推断能确定 res 的类型是 string,则不会报错。

五、泛型

  • 用来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。简单的说,泛型就是把类型当成参数。例:

    function identity <T, U>(value: T, message: U) : T {
      console.log(message);
      return value;
    }
    identity<Number, string>(68, “hello”);
    
  • 调用时可以不传类型,编译器能够自动选择这些类型:

    identity(68, "hello")
    
  • 泛型接口:

    interface Identities<V, M> {
      value: V,
      message: M
    }
    function identity<T, U> (value: T, message: U): Identities<T, U> {
      return { value, message };
    }
    identity(68, "hello”);
    
  • 泛型类:

    class IdentityClass<T> {
      value: T
      constructor(value: T) {
        this.value = value
      }
      getIdentity(): T {
        return this.value
      }
    }
    const myNumberClass = new IdentityClass<Number>(68);
    
  • 确保属性存在

    interface Length {
      length: number;
    }
    function identity<T extends Length>(arg: T): T {
      console.log(arg.length); 
      return arg;
    }
    

    这样一来,identity 只能接受有 length 属性的参数了

  • 检查对象上的键是否存在

    function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }