# TypeScript

TypeScriptJavaScript 的超集,提供了类型系统,可以在编译时进行类型检查,帮助开发者在编码过程中捕获潜在的错误。通过类型注解和类型推断,开发者可以声明变量、函数、类等的类型,并且编译器会根据这些类型信息进行静态检查。这样的类型检查能够提供更好的代码智能提示、错误提示和重构支持,从而提高代码的可靠性和可维护性。

# 变量和类型

  1. letconst: 用于声明变量,let 声明的变量是可变的(可重新赋值),const 声明的变量是不可变的(常量)。

  2. 基本类型:

    • number: 表示数字类型,如 let age: number = 25;
    • string: 表示字符串类型,如 let name: string = "John";
    • boolean: 表示布尔类型,值为 truefalse,如 let isDone: boolean = false;
    • nullundefined: 表示空或未定义的值,如 let x: null = null;
    • symbol: 表示唯一的符号值,如 let key: symbol = Symbol("key");
  3. 数组类型:

    • 使用 type[] 表示元素类型为特定类型的数组,如 let numbers: number[] = [1, 2, 3];
    • 使用泛型数组类型 Array<type>,如 let names: Array<string> = ["Alice", "Bob"];
  4. 对象类型:

    • 使用对象字面量表示法 {} 声明对象,如 let person: { name: string, age: number } = { name: "John", age: 25 };

    • 使用接口 interface 定义对象类型,如:

      interface Person {
        name: string;
        age: number;
      }
      let person: Person = { name: "John", age: 25 };
      
      1
      2
      3
      4
      5
  5. 函数类型:

    • 使用函数类型声明,如:

      let add: (x: number, y: number) => number = function (x, y) {
        return x + y;
      };
      
      1
      2
      3
    • 使用接口 interface 定义函数类型,如:

      interface AddFunction {(x: number, y: number): number;}
      let add: AddFunction = function(x, y) { return x + y; };
      
      1
      2
  6. 类类型:

    • 使用 class 关键字定义类,如:

      class Person {
        name: string;
        age: number;
        constructor(name: string, age: number) {
          this.name = name;
          this.age = age;
        }
      }
      let person: Person = new Person("Melon", 23);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
  7. 元组 Tuple 类型

    • 元组是一种固定长度且类型可不同的数组。可以使用元组类型来定义具有特定顺序和类型的元素组合。例如:

      let tuple: [string, number] = ["apple", 10];
      
      1
  8. 枚举 Enum 类型:

    • 枚举用于定义一组命名的常量值。它们可以用作标识符或属性值。例如:

      enum Color {
        Red,
        Green,
        Blue
      }
      let color: Color = Color.Red;
      
      1
      2
      3
      4
      5
      6

# 函数和参数

  1. 函数声明:

    function functionName(parameters: type): returnType {
      // 函数体
    }
    
    1
    2
    3
  2. 函数表达式:

    const functionName = function(parameters: type): returnType {
      // 函数体
    };
    
    1
    2
    3
  3. 箭头函数表达式:

    const functionName = (parameters: type): returnType => {
      // 函数体
    };
    
    1
    2
    3
  4. 在函数声明和表达式中,可以指定参数的类型和返回值的类型。例如:

    function add(x: number, y: number): number {
      return x + y;
    }
    
    1
    2
    3
  5. 函数参数可以是必需的或可选的,并且可以具有默认值。例如:

    function greet(name: string, age?: number): string {
      if (age) {
        return `Hello, ${name}! You are ${age} years old.`;
      } else {
        return `Hello, ${name}!`;
      }
    }
    // age 参数是可选的,可以传入或不传入。如果不传入,则默认为 undefined
    
    1
    2
    3
    4
    5
    6
    7
    8
  6. 可以使用剩余参数 Rest Parameters 来接收变长的参数列表。剩余参数使用 ... 语法来表示,将传入的参数打包为一个数组。例如:

    function sum(...numbers: number[]): number {
      return numbers.reduce((acc, curr) => acc + curr, 0);
    }
    // sum 函数接收任意数量的数字参数,并返回它们的总和。
    
    1
    2
    3
    4
  7. 函数还可以具有函数类型的参数和返回值。这样可以实现函数的高阶和灵活的使用。例如:

    function calculate(operation: (x: number, y: number) => number, x: number, y: number): number {
      return operation(x, y);
    }
    function add(x: number, y: number): number {
      return x + y;
    }
    let result: number = calculate(add, 5, 3);
    // calculate 函数接收一个函数类型的 operation 参数,该函数接受两个参数并返回一个数字。然后,我们将 add 函数作为参数传递给 calculate 函数,并传入额外的数字参数,最后得到计算结果。
    
    1
    2
    3
    4
    5
    6
    7
    8
  8. 使用 Interface 声明

    interface BinaryOperation {
      (x: number, y: number): number;
    }
    
    function calculate(operation: BinaryOperation, x: number, y: number): number {
      return operation(x, y);
    }
    
    function add(x: number, y: number): number {
      return x + y;
    }
    
    let result: number = calculate(add, 5, 3);
    // 我们定义了一个名为 BinaryOperation 的接口,它描述了一个接受两个 number 类型参数并返回 number 类型的函数。然后,我们将 add 函数的类型与 BinaryOperation 接口进行匹配,并将其作为参数传递给 calculate 函数。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  9. 行参类型为多个时,可以使用重载签名

    function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
    function sum(num1, num2) {
      return num1 + num2;
    }
    
    const sumRes1 = sum(20, 20);
    const sumRes2 = sum('20', '20');
    
    1
    2
    3
    4
    5
    6
    7
  10. 函数签名

    // 函数签名
    type MyAddFunction = (x: number, y: number) => number;
    
    // 函数签名的实现
    const add: MyAddFunction = (x, y) => {
      return x + y;
    };
    
    1
    2
    3
    4
    5
    6
    7
  11. 重载函数

    // 函数重载
    function calculateArea(shape: string): number;
    function calculateArea(shape: string, width: number, height: number): number;
    
    // 函数重载的实现
    function calculateArea(shape: string, width?: number, height?: number): number {
      if (shape === 'rectangle') {
        return width! * height!;
      } else if (shape === 'square') {
        return width! * width!;
      } else {
        throw new Error('Invalid shape');
      }
    }
    
    // 调用函数
    const rectangleArea = calculateArea('rectangle', 5, 10);
    const squareArea = calculateArea('square', 7);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  12. 在函数中重载签名和 interface 声明的区别

  13. 函数重载签名:

    • 函数重载签名是通过多次使用函数声明并提供不同的参数类型或参数数量来定义函数的多个形式。

    • 函数重载签名用于声明函数的不同重载形式,而函数实现则只需要编写一次。

    • 函数重载签名使用 function 关键字和函数名称,然后按照不同的参数类型或参数数量提供多个函数声明。

    • 示例:

      function calculateArea(shape: string): number;
      function calculateArea(shape: string, width: number, height: number): number;
      function calculateArea(shape: string, width?: number, height?: number): number {
        // 实现函数体
      }
      
      1
      2
      3
      4
      5
  14. 接口声明:

    • 接口声明是通过 interface 关键字定义一个新的类型,用于描述函数的形状(参数和返回值类型)。

    • 接口声明用于声明函数的类型,并且通常需要提供完整的函数形状,包括参数和返回值类型。

    • 接口声明可以用于函数类型的变量、函数参数或函数返回值的类型注解。

    • 示例:

      interface CalculateAreaFn {
        (shape: string): number;
      }
      
      const calculateArea: CalculateAreaFn = (shape) => {
        // 实现函数体
      };
      
      1
      2
      3
      4
      5
      6
      7

在使用上的区别:

  • 函数重载签名适用于在函数定义时声明多个形式的函数,并在函数实现中统一处理不同的情况。适用于需要对不同的参数类型或参数数量做特定处理的情况。
  • 接口声明适用于描述函数类型的类型注解,用于指定函数参数和返回值的类型。适用于变量、参数或返回值的类型注解以及自定义类型。

# 类和对象

​ 在 TypeScript 中,可以使用 class 关键字来定义类,通过类可以创建对象。类是面向对象编程的核心概念之一,它允许封装数据和方法,并通过实例化类来创建对象。

​ 以下是一个简单的类和对象的示例:

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const person1 = new Person("John", 25);
person1.greet();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

​ 在上述示例中,我们定义了一个 Person 类,它具有 nameage 两个属性,以及一个 greet 方法。constructor 是一个特殊的方法,用于在创建对象时进行初始化操作。我们使用 new 关键字创建了一个 Person 类的实例,传入相应的参数进行初始化,并调用了 greet 方法。

类中的属性和方法可以使用 publicprivateprotected 访问修饰符进行修饰:

  • public(默认):公共访问修饰符,属性和方法可以在类内部和外部访问。
  • private:私有访问修饰符,属性和方法只能在类内部访问。
  • protected:受保护访问修饰符,属性和方法可以在类内部和派生类(子类)中访问。

例如,将 name 属性修改为私有属性:

class Person {
  private name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const person1 = new Person("John", 25);
console.log(person1.name); // Error: Property 'name' is private
person1.greet(); // Hello, my name is John and I am 25 years old.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

除了属性和方法,类还可以具有静态属性和静态方法。静态属性和静态方法属于类本身,而不是类的实例。可以使用 static 关键字进行声明和访问。例如:

class MathUtils {
  static PI: number = 3.14159;

  static calculateCircumference(radius: number): number {
    return 2 * this.PI * radius;
  }
}

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.calculateCircumference(5)); // 31.4159
1
2
3
4
5
6
7
8
9
10

在上述示例中,PI 是一个静态属性,可以通过类名直接访问。calculateCircumference 是一个静态方法,也可以通过类名直接调用。

# 接口和泛型

  1. 接口 Interfaces :

    • 接口是一种用于定义对象的结构和类型的方式。通过接口,可以定义对象应该具有的属性和方法,并指定它们的类型。

    • 接口通过 interface 关键字进行声明,可以在其他类型中使用接口进行类型注解,以确保对象的结构和类型满足接口的要求。

    • 示例:

      interface Person {
        name: string;
        age: number;
        greet(): void;
      }
      
      function sayHello(person: Person) {
        console.log(`Hello, ${person.name}!`);
        person.greet();
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
  2. 泛型 Generics :

    • 泛型允许在编写代码时使用类型参数,以增加代码的灵活性和重用性。通过泛型,可以编写可以适用于多种类型的函数、类和接口。

    • 泛型使用尖括号 <T> 来表示类型参数,并在函数或类中使用它。

    • 示例:

      function identity<T>(arg: T): T {
        return arg;
      }
      
      let result = identity<string>("Hello");
      
      1
      2
      3
      4
      5

# 模块化编程

  1. CommonJS 模块化语法:

    • CommonJS 是一种模块化规范,最初用于 Node.js 环境。

    • 使用 require 关键字引入模块,使用 module.exports 导出模块。

    • 示例:

      // 模块导出
      module.exports = {
        greet: function(name) {
          console.log(`Hello, ${name}!`);
        },
      };
      
      // 模块导入
      const greeter = require("./greeter");
      greeter.greet("John");
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
  2. ES 模块化语法:

    • ES 模块是JavaScript 的官方模块化规范,通过 importexport 关键字进行模块的导入和导出。

    • ES 模块可以在浏览器环境和现代 JavaScript 运行时中使用。

    • 示例:

      // 模块导出
      export function greet(name) {
        console.log(`Hello, ${name}!`);
      }
      
      // 模块导入
      import { greet } from "./greeter";
      greet("John");
      
      1
      2
      3
      4
      5
      6
      7
      8

# asyncpromise

当使用 asyncawait 结合 Promise 时,可以实现更简洁、易读的异步代码编写。下面是一个完整的案例,说明如何使用 asyncawait 处理异步操作:

function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
      const data = "Hello, Promise!";
      resolve(data); // 异步操作成功,将结果传递给 resolve
      // reject(new Error("Something went wrong!")); // 异步操作失败,将错误传递给 reject
    }, 2000);
  });
}

async function fetchDataAsync(): Promise<void> {
  try {
    const data = await fetchData(); // 等待异步操作完成并获取结果
    console.log(data); // 在异步操作成功后执行下一步操作
  } catch (error) {
    console.error(error); // 在异步操作失败时处理错误
  } finally {
    console.log("Async operation completed."); // 不论异步操作成功或失败,都会执行的操作
  }
}

fetchDataAsync();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

在上面的示例中,我们定义了一个 fetchData 函数,它返回一个 Promise 对象,模拟一个异步操作。然后,我们定义了一个 fetchDataAsync 函数,它使用 async 关键字将函数标记为异步函数,并返回一个 Promise<void> 类型。在 fetchDataAsync 函数中,我们使用 await 关键字等待异步操作的结果,并将结果赋值给变量 data

try 块中,我们处理异步操作成功的情况,打印出结果。如果异步操作失败,会进入 catch 块,捕获并处理错误。无论成功或失败,finally 块中的代码都会执行。最后,我们调用 fetchDataAsync 函数来启动异步操作。

通过使用 asyncawait,我们可以将异步代码看起来像同步代码一样,增加了代码的可读性和可维护性。使用 try/catch 块可以更方便地处理异步操作中的错误情况。同时,我们仍然能够利用 Promise 的特性,如链式调用、并行操作等。

请注意,async 函数内部必须使用 await 来等待 Promise 的完成或其他 async 函数的调用。否则,函数将立即返回一个已解决的 Promise,而不会等待异步操作完成。