TypeScript 基础 | 青训营

119 阅读7分钟

3. 学习笔记:CSS选择器、继承与布局

本课程从TS基础入手,由基础类型一步步延伸到高级类型,通过示例来深入浅出的了解TypeScript的各种姿势。

TypeScript 和 JavaScript 的区别

image.png

TypeScript和JavaScript是两种编程语言,它们之间有一些重要的区别。下面是它们的主要区别:

  1. 静态类型 vs. 动态类型: JavaScript是一种动态类型语言,变量的类型在运行时才确定。而TypeScript是一种静态类型语言,变量的类型在编译时就要确定,并且在代码编写过程中需要显式地声明类型。

  2. 类型注解: 在TypeScript中,我们可以使用类型注解来明确变量的类型。这有助于提供更好的代码提示和类型检查,帮助开发者在编码阶段就发现潜在的类型错误。

  3. 编译过程: JavaScript是一种解释型语言,代码在运行前不需要编译。而TypeScript代码需要经过编译过程,将TypeScript代码转换为JavaScript代码,然后才能在浏览器或Node.js中运行。

  4. ECMAScript标准支持: TypeScript是在ECMAScript标准之上的超集,它包含了所有JavaScript的特性,并且额外增加了静态类型系统和其他一些新特性。这意味着TypeScript可以无缝地使用JavaScript中的代码和库,并且还可以使用最新的ECMAScript特性。

  5. 开发工具支持: 由于TypeScript提供了更丰富的类型信息,开发工具(如IDE、编辑器等)可以提供更强大的代码提示、补全和错误检查功能,使得开发过程更高效。

  6. 适用场景: JavaScript适用于快速的Web开发和原型开发,特别适合在前端进行动态交互。而TypeScript适用于大型项目和需要更严格类型检查的场景,它可以帮助减少潜在的类型错误,提高代码的可维护性和可读性。

总体而言,JavaScript是一门非常灵活且广泛使用的语言,而TypeScript是JavaScript的增强版本,通过引入静态类型系统和其他特性,为大型项目提供了更好的结构和类型安全性。选择使用JavaScript还是TypeScript取决于项目的规模、复杂性和开发团队的偏好。

TypeScript基础

本部分将介绍TypeScript中的基础类型、函数类型、接口和类:

  1. 基础类型
    TypeScript支持JavaScript的基础类型,如:number、string、boolean、null、undefined、symbol等。此外,TypeScript还增加了一些额外的类型,如:any、void、never、unknown等。

  2. 函数类型

image.png

在TypeScript中,可以为函数定义参数类型和返回值类型。例如:

function add(a: number, b: number): number {
  return a + b;
}

这里的函数add接受两个参数a和b,都是number类型,并且返回值也是number类型。

  1. 接口 (interface)
    接口是用于描述对象的形状的一种方式。通过接口,可以明确指定对象中应该包含哪些属性和它们的类型。例如:

    interface Person {
      name: string;
      age: number;
    }
    
    function greet(person: Person) {
      return `Hello, ${person.name}! You are ${person.age} years old.`;
    }
    

    这里定义了一个名为Person的接口,它包含name和age两个属性,然后在函数greet中使用该接口来约束参数person的类型。

  2. 类 (class)
    TypeScript支持类的概念,可以使用class关键字来定义类。类可以具有属性和方法,并且可以使用构造函数初始化对象的属性。例如:

    class Animal {
      name: string;
      
      constructor(name: string) {
        this.name = name;
      }
    
      makeSound() {
        console.log("Animal makes a sound");
      }
    }
    
    const dog = new Animal("Dog");
    console.log(dog.name); // 输出 "Dog"
    dog.makeSound(); // 输出 "Animal makes a sound"
    

    这里定义了一个名为Animal的类,它有一个属性name和一个方法makeSound。使用构造函数创建一个名为dog的Animal实例,并调用makeSound方法。

4.1 访问修饰符 private、public 和 protected
在TypeScript类中,可以使用privatepublicprotected这三个访问修饰符来控制类成员的可见性和访问权限。

  1. public
    public是默认的访问修饰符,如果不指定访问修饰符,默认为public。使用public修饰的成员可以在类内部和类外部访问。

    class MyClass {
      public name: string;
    
      constructor(name: string) {
        this.name = name;
      }
    
      public sayHello() {
        console.log(`Hello, ${this.name}!`);
      }
    }
    
    const obj = new MyClass("John");
    console.log(obj.name); // 输出 "John"
    obj.sayHello(); // 输出 "Hello, John!"
    
  2. private
    private修饰符用于限制成员只能在类的内部访问,类的外部无法访问该成员。

    class MyClass {
      private age: number;
    
      constructor(age: number) {
        this.age = age;
      }
    
      public getAge() {
        return this.age; // 在类的内部可以访问私有成员
      }
    }
    
    const obj = new MyClass(25);
    console.log(obj.getAge()); // 输出 25
    console.log(obj.age); // 错误,age是私有成员,无法在类外部访问
    
  3. protected
    protected修饰符允许成员在类内部以及派生类中访问,但在类的外部是不可见的。

    class Parent {
      protected familyName: string;
    
      constructor(name: string) {
        this.familyName = name;
      }
    }
    
    class Child extends Parent {
      public getFullName() {
        return this.familyName + " Smith"; // 在派生类中可以访问 protected 成员
      }
    }
    
    const child = new Child("John");
    console.log(child.getFullName()); // 输出 "John Smith"
    console.log(child.familyName); // 错误,familyName是 protected 成员,无法在类外部访问
    

使用这些访问修饰符,可以控制类成员的可见性,从而增强类的封装性和安全性。根据需求选择合适的访问修饰符,是良好的编程实践。

4.2 抽象类和接口
在TypeScript中,抽象类和接口是两种不同的机制,用于对类进行约束和抽象。它们分别用于不同的场景:

  1. 抽象类(Abstract Class): 抽象类是用来作为其他类的基类的,它本身不能被实例化。抽象类可以包含抽象方法(没有具体实现的方法)和具体方法(有具体实现的方法)。子类继承抽象类后,必须实现抽象方法。

    abstract class Animal {
      abstract makeSound(): void;
    
      move(): void {
        console.log("Animal is moving");
      }
    }
    
    class Dog extends Animal {
      makeSound(): void {
        console.log("Dog barks");
      }
    }
    
    const dog = new Dog();
    dog.makeSound(); // 输出 "Dog barks"
    dog.move(); // 输出 "Animal is moving"
    
  2. 接口(Interface): 接口是用来描述对象的形状的,它定义了对象应该具有哪些属性和方法。接口只能描述类的公共部分,不包含具体实现。

    interface Person {
      name: string;
      age: number;
      sayHello(): void;
    }
    
    class Student implements Person {
      name: string;
      age: number;
    
      constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
      }
    
      sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
      }
    }
    
    const student = new Student("John", 25);
    student.sayHello(); // 输出 "Hello, my name is John and I am 25 years old."
    

    接口可以用来约束类的结构,要求类必须包含接口中定义的属性和方法。

总结:

  • 抽象类用于作为其他类的基类,并可以包含抽象方法和具体方法,需要被子类继承和实现抽象方法。
  • 接口用于描述对象的形状,约束类必须包含接口中定义的属性和方法。一个类可以实现多个接口。

TypeScript进阶

TypeScript还包含很多其他功能,如泛型、枚举、命名空间等,以及一些高级的类型概念,包括联合类型、交叉类型、类型断言、类型别名和泛型。它们可以帮助我们更灵活地定义和使用类型。这些在大型项目中非常有用。通过引入类型检查和其他特性,TypeScript可以帮助开发者编写更健壮、可维护的代码。

  1. 联合类型(Union Types)
    联合类型允许一个变量或参数具有多种可能的类型。使用竖线(|)分隔每个类型。

    let value: string | number;
    value = "hello"; // 合法
    value = 42; // 合法
    value = true; // 错误,布尔类型不是联合类型的其中一种
    
  2. 交叉类型(Intersection Types)
    交叉类型将多个类型合并成一个类型,新类型具备所有原始类型的特性。

    type Point = { x: number; y: number };
    type Color = { color: string };
    type PointWithColor = Point & Color;
    
    const point: PointWithColor = { x: 10, y: 20, color: "red" };
    
  3. 类型断言(Type Assertion)
    类型断言允许开发者在某些情况下手动指定变量的类型,告诉编译器变量的实际类型。使用尖括号语法或as关键字。

    let someValue: any = "this is a string";
    let strLength: number = (someValue as string).length;
    
  4. 类型别名(Type Alias)
    类型别名可以为类型起一个新名字,方便在多个地方复用复杂的类型定义。

    type UserID = string | number;
    type User = {
      id: UserID;
      name: string;
    };
    
  5. 泛型(Generics
    泛型允许在函数、类、接口等中使用类型参数,以便在使用时指定具体的类型。

    function identity<T>(arg: T): T {
      return arg;
    }
    
    const result = identity<number>(42);
    

    在上述例子中,identity函数使用了泛型类型参数<T>,可以接受任意类型的参数并返回相同类型的值。

  6. 枚举(Enum)
    在TypeScript中,枚举(Enum)是一种特殊的数据类型,用于为一组相关的常量赋予有意义的名字。枚举类型在JavaScript中并不存在,它是TypeScript为了增强代码可读性和可维护性而引入的概念。

使用枚举可以定义一组有序的常量,每个常量都有一个关联的名称和数值。以下是枚举的基本用法:

enum Direction {
  Up,       // 0
  Down,     // 1
  Left,     // 2
  Right     // 3
}

let dir: Direction = Direction.Up;
console.log(dir); // 输出 0

// 使用枚举值
if (dir === Direction.Up) {
  console.log("向上");
} else if (dir === Direction.Down) {
  console.log("向下");
} else if (dir === Direction.Left) {
  console.log("向左");
} else if (dir === Direction.Right) {
  console.log("向右");
}

在上述例子中,我们定义了一个名为Direction的枚举,它包含四个成员:UpDownLeftRight,分别对应数值0、1、2和3。然后我们创建一个名为dir的变量,它的类型是Direction,并将其赋值为Direction.Up,输出的结果为0,因为Up对应的数值是0。

枚举成员默认从0开始自增,也可以手动指定数值:

enum StatusCode {
  OK = 200,
  NotFound = 404,
  ServerError = 500
}

此时,StatusCode.OK对应的数值是200,StatusCode.NotFound对应的数值是404,以此类推。

枚举还有一些其他特性,例如反向映射、枚举成员类型等。通过使用枚举,我们可以提高代码的可读性,使得一组相关的常量更加易于理解和维护。