MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Typescript的基本语法详解

2021-03-276.3k 阅读

一、TypeScript 基础类型

在 TypeScript 中,基础类型是构建更复杂类型的基石。

(一)布尔类型(boolean)

布尔类型只有两个值:truefalse,这与 JavaScript 中的布尔值一致。

let isDone: boolean = false;

这里我们声明了一个变量 isDone,类型为 boolean,并初始化为 false

(二)数字类型(number)

TypeScript 中的数字类型和 JavaScript 一样,都是双精度 64 位浮点值。可以表示整数和小数。

let myNumber: number = 42;
let pi: number = 3.14;

除了十进制,还支持二进制、八进制和十六进制表示。

let binaryNumber: number = 0b1010; // 二进制 1010 表示十进制 10
let octalNumber: number = 0o755; // 八进制 755 表示十进制 493
let hexadecimalNumber: number = 0xFFFF; // 十六进制 FFFF 表示十进制 65535

(三)字符串类型(string)

字符串用于表示文本数据。可以使用单引号 ' 或双引号 " 来定义字符串。

let greeting: string = 'Hello, World!';
let anotherGreeting: string = "Goodbye, World!";

TypeScript 还支持模板字符串,它允许嵌入表达式。

let name: string = 'John';
let message: string = `Hello, ${name}! How are you?`;

(四)null 和 undefined

在 TypeScript 中,nullundefined 有自己的类型,分别为 nullundefined。它们是所有类型的子类型。

let nothing: null = null;
let unassigned: undefined = undefined;

默认情况下,nullundefined 可以赋值给任何类型。但在严格模式下(strictNullChecks 开启),它们只能赋值给 void 和它们各自的类型。

(五)void 类型

void 类型表示没有任何类型。通常用于函数没有返回值的情况。

function logMessage(message: string): void {
    console.log(message);
}

这里 logMessage 函数返回 void,因为它没有返回值。

(六)never 类型

never 类型表示那些永不存在的值的类型。例如,一个总是抛出异常或无限循环的函数返回值类型就是 never

function throwError(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

(七)any 类型

any 类型表示任意类型。当你不确定一个值的类型时,可以使用 any

let value: any = 'a string';
value = 42;
value = true;

虽然 any 类型提供了很大的灵活性,但过度使用会失去 TypeScript 的类型检查优势。

(八)unknown 类型

unknown 类型和 any 类似,也表示任意类型。但与 any 不同的是,unknown 类型的值在使用前必须进行类型检查。

let something: unknown = 'a value';
// 下面这行代码会报错,因为 unknown 类型不能直接赋值给 string 类型
// let str: string = something; 

if (typeof something ==='string') {
    let str: string = something;
}

(九)枚举类型(enum)

枚举类型用于定义一组命名的常量。它可以让代码更易读和维护。

enum Color {
    Red,
    Green,
    Blue
}

let myColor: Color = Color.Green;

默认情况下,枚举成员从 0 开始自动编号。也可以手动指定值。

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

这里 Up 的值为 1Down 的值为 2Left 的值为 3Right 的值为 4

二、变量声明与类型注解

在 TypeScript 中,变量声明与 JavaScript 类似,但可以添加类型注解来明确变量的类型。

(一)变量声明方式

  1. letconst
    • let 声明的变量具有块级作用域。
    • const 声明的是常量,一旦赋值后不能再改变。
{
    let localVar: number = 10;
    const constantValue: string = 'constant';
    localVar = 20; // 合法
    // constantValue = 'new value'; // 报错,常量不能重新赋值
}
  1. var var 声明的变量具有函数作用域,在现代 TypeScript 代码中,建议优先使用 letconst

(二)类型注解

  1. 基本类型注解 在声明变量时,可以在变量名后加上 : 和类型来进行类型注解。
let age: number;
age = 30;
  1. 复杂类型注解 对于对象、数组等复杂类型,也可以进行类型注解。
let person: { name: string; age: number };
person = { name: 'Alice', age: 25 };
  1. 函数参数和返回值类型注解
function add(a: number, b: number): number {
    return a + b;
}

这里函数 add 的参数 ab 类型为 number,返回值类型也为 number

三、函数

函数在 TypeScript 中是一等公民,并且可以进行详细的类型定义。

(一)函数定义与类型

  1. 函数声明
function greet(name: string): string {
    return `Hello, ${name}!`;
}
  1. 函数表达式
let greetFunc: (name: string) => string = function (name: string): string {
    return `Hello, ${name}!`;
};

这里 greetFunc 是一个函数类型的变量,其类型为 (name: string) => string,表示接受一个 string 类型参数并返回一个 string 类型值的函数。

(二)可选参数和默认参数

  1. 可选参数 在参数名后加上 ? 表示该参数是可选的。
function printMessage(message: string, prefix?: string) {
    if (prefix) {
        console.log(prefix + ':'+ message);
    } else {
        console.log(message);
    }
}

printMessage('Hello');
printMessage('World', 'Info');
  1. 默认参数 可以给参数设置默认值。
function greet(name = 'Guest'): string {
    return `Hello, ${name}!`;
}

console.log(greet());
console.log(greet('John'));

(三)剩余参数

使用 ... 来表示剩余参数,它将所有剩余的参数收集到一个数组中。

function sum(...numbers: number[]): number {
    return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3));

(四)函数重载

函数重载允许一个函数接受不同数量或类型的参数,通过为同一个函数定义多个函数类型签名来实现。

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    if (typeof a === 'number' && typeof b === 'number') {
        return a + b;
    } else if (typeof a ==='string' && typeof b ==='string') {
        return a + b;
    }
    return null;
}

console.log(add(1, 2));
console.log(add('Hello, ', 'World'));

这里定义了两个函数重载签名,一个接受两个 number 类型参数返回 number,另一个接受两个 string 类型参数返回 string。实际的函数实现根据参数类型进行不同的操作。

四、数组与元组

数组和元组是 TypeScript 中常用的数据结构,它们有不同的类型表示和使用方式。

(一)数组

  1. 类型注解 可以使用 type[]Array<type> 来表示数组类型。
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];
  1. 数组方法的类型检查 TypeScript 会对数组的方法进行类型检查。例如,push 方法的参数类型必须与数组元素类型一致。
let numbers: number[] = [1, 2, 3];
numbers.push(4); // 合法
// numbers.push('five'); // 报错,类型不匹配

(二)元组

元组是一种特殊的数组,它允许表示一个固定长度且元素类型已知的数组。

let point: [number, number] = [10, 20];

这里 point 是一个元组,第一个元素类型为 number,第二个元素类型也为 number。访问元组元素时,类型检查会确保访问的元素类型正确。

let x: number = point[0];
// let y: string = point[1]; // 报错,类型不匹配

元组还支持可选元素和剩余元素。

let user: [string, number, boolean?] = ['John', 30];
let colors: [string,...string[]] = ['red', 'green', 'blue'];

这里 user 元组的第三个元素是可选的,colors 元组第一个元素是 string 类型,后面可以有多个 string 类型的剩余元素。

五、对象类型

在 TypeScript 中,对象类型用于描述对象的结构。

(一)对象字面量类型

可以通过对象字面量来定义对象类型。

let person: { name: string; age: number } = { name: 'Bob', age: 28 };

这里定义了一个 person 对象,它必须包含 name 属性,类型为 string,以及 age 属性,类型为 number

(二)接口(interface)

接口是一种更强大的方式来定义对象类型。它可以被重复使用,用于定义类的形状或对象的类型。

interface Person {
    name: string;
    age: number;
}

let alice: Person = { name: 'Alice', age: 25 };

接口还支持可选属性和只读属性。

interface Product {
    name: string;
    price: number;
    description?: string;
    readonly id: number;
}

let book: Product = { name: 'TypeScript Basics', price: 29.99, id: 123 };
// book.id = 456; // 报错,id 是只读属性

(三)类型别名(type alias)

类型别名也可以用于定义对象类型,与接口有一些相似之处,但也有不同。

type User = {
    username: string;
    email: string;
};

let newUser: User = { username: 'testuser', email: 'test@example.com' };

接口和类型别名的主要区别之一是,接口可以合并声明,而类型别名不行。

interface Point {
    x: number;
}
interface Point {
    y: number;
}

let myPoint: Point = { x: 10, y: 20 };

// type PointAlias {
//     x: number;
// }
// type PointAlias {
//     y: number;
// } // 报错,不能重复定义类型别名

六、类

类是面向对象编程的核心概念,TypeScript 对类提供了完整的支持。

(一)类的定义与实例化

  1. 类的定义
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    speak() {
        console.log(`${this.name} makes a sound.`);
    }
}

这里定义了一个 Animal 类,有一个 name 属性和一个构造函数 constructor 用于初始化 name,还有一个 speak 方法。 2. 类的实例化

let dog = new Animal('Buddy');
dog.speak();

(二)访问修饰符

  1. public public 是默认的访问修饰符,表示属性或方法可以在类的内部和外部访问。
class Car {
    public brand: string;
    constructor(brand: string) {
        this.brand = brand;
    }
    public displayBrand() {
        console.log(`The car brand is ${this.brand}`);
    }
}

let myCar = new Car('Toyota');
console.log(myCar.brand);
myCar.displayBrand();
  1. private private 修饰的属性或方法只能在类的内部访问。
class BankAccount {
    private balance: number;
    constructor(initialBalance: number) {
        this.balance = initialBalance;
    }
    private updateBalance(amount: number) {
        this.balance += amount;
    }
    deposit(amount: number) {
        this.updateBalance(amount);
    }
    getBalance() {
        return this.balance;
    }
}

let account = new BankAccount(1000);
// console.log(account.balance); // 报错,balance 是私有属性
// account.updateBalance(500); // 报错,updateBalance 是私有方法
account.deposit(500);
console.log(account.getBalance());
  1. protected protected 修饰的属性或方法可以在类的内部和子类中访问。
class Shape {
    protected color: string;
    constructor(color: string) {
        this.color = color;
    }
    protected displayColor() {
        console.log(`The shape color is ${this.color}`);
    }
}

class Circle extends Shape {
    radius: number;
    constructor(color: string, radius: number) {
        super(color);
        this.radius = radius;
    }
    showDetails() {
        this.displayColor();
        console.log(`The circle has a radius of ${this.radius}`);
    }
}

let circle = new Circle('red', 5);
// console.log(circle.color); // 报错,color 是受保护属性
// circle.displayColor(); // 报错,displayColor 是受保护方法
circle.showDetails();

(三)继承

通过 extends 关键字实现类的继承。

class Vehicle {
    wheels: number;
    constructor(wheels: number) {
        this.wheels = wheels;
    }
    move() {
        console.log(`The vehicle with ${this.wheels} wheels is moving.`);
    }
}

class Car extends Vehicle {
    brand: string;
    constructor(brand: string, wheels: number) {
        super(wheels);
        this.brand = brand;
    }
    drive() {
        console.log(`Driving a ${this.brand} car with ${this.wheels} wheels.`);
    }
}

let myCar = new Car('Ford', 4);
myCar.move();
myCar.drive();

这里 Car 类继承自 Vehicle 类,拥有 Vehicle 类的属性和方法,并添加了自己的 brand 属性和 drive 方法。

(四)抽象类

抽象类是不能被实例化的类,它通常作为其他类的基类,包含一些抽象方法。抽象方法是没有具体实现的方法,必须在子类中实现。

abstract class Figure {
    abstract area(): number;
    abstract perimeter(): number;
}

class Rectangle extends Figure {
    width: number;
    height: number;
    constructor(width: number, height: number) {
        super();
        this.width = width;
        this.height = height;
    }
    area() {
        return this.width * this.height;
    }
    perimeter() {
        return 2 * (this.width + this.height);
    }
}

// let figure = new Figure(); // 报错,不能实例化抽象类
let rectangle = new Rectangle(5, 3);
console.log(rectangle.area());
console.log(rectangle.perimeter());

七、泛型

泛型是 TypeScript 中非常强大的特性,它允许我们在定义函数、类、接口时不指定具体类型,而是在使用时再指定类型。

(一)泛型函数

function identity<T>(arg: T): T {
    return arg;
}

let result1 = identity<number>(42);
let result2 = identity<string>('hello');

这里定义了一个泛型函数 identity<T> 表示类型参数,T 可以是任何类型。在调用函数时,可以通过 <> 来指定 T 的具体类型。

(二)泛型接口

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

这里定义了一个泛型接口 GenericIdentityFn,它描述了一个接受类型为 T 的参数并返回类型为 T 的值的函数。

(三)泛型类

class Stack<T> {
    private items: T[] = [];
    push(item: T) {
        this.items.push(item);
    }
    pop(): T | undefined {
        return this.items.pop();
    }
}

let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop());

let stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');
console.log(stringStack.pop());

这里 Stack 类是一个泛型类,<T> 表示栈中元素的类型。不同实例化的 Stack 类可以存储不同类型的元素。

(四)泛型约束

有时我们需要对泛型类型进行一些约束,比如要求传入的类型必须包含某个属性。

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

let result = loggingIdentity('hello');
// let badResult = loggingIdentity(42); // 报错,number 类型不满足 Lengthwise 接口约束

这里定义了一个接口 Lengthwise,要求类型必须有 length 属性。泛型函数 loggingIdentityT 进行了约束,只有满足 Lengthwise 接口的类型才能作为 T 的值传入。

八、类型断言

类型断言用于告诉编译器某个值的实际类型,当你比编译器更清楚某个值的类型时可以使用。

(一)语法

  1. 尖括号语法
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
  1. as 语法
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;

在 TypeScript 中,当在 JSX 中使用时,只能使用 as 语法。

(二)类型断言的用途

  1. any 类型转换为更具体的类型
function getValue(): any {
    return 'a value';
}

let value = getValue();
let length = (value as string).length;
  1. 绕过编译器的类型检查 但要谨慎使用,因为如果断言的类型不正确,可能会导致运行时错误。
let num: number = 10;
// 下面这行代码虽然绕过了类型检查,但在运行时可能出错
let str: string = num as string; 

九、类型兼容性

TypeScript 在进行类型检查时,会根据类型兼容性规则来判断一个类型是否可以赋值给另一个类型。

(一)基本类型兼容性

  1. 数字类型兼容性 数字类型之间是兼容的,例如 numbernumber 兼容,numberbigint 不兼容。
let num1: number = 10;
let num2: number = num1;
// let bigNum: bigint = num1; // 报错,类型不兼容
  1. 布尔类型兼容性 boolean 类型只有 truefalse 两个值,不同的 boolean 类型变量之间是兼容的。

(二)对象类型兼容性

对象类型兼容性基于结构子类型。如果一个对象类型的所有属性在另一个对象类型中都有兼容的类型,那么这两个对象类型是兼容的。

interface Point {
    x: number;
}

interface Circle {
    x: number;
    radius: number;
}

let point: Point = { x: 10 };
let circle: Circle = { x: 10, radius: 5 };
point = circle; // 合法,Circle 类型兼容 Point 类型
// circle = point; // 报错,Point 类型不兼容 Circle 类型,缺少 radius 属性

(三)函数类型兼容性

函数类型兼容性比较复杂。对于函数参数,是双向协变的,但在严格模式下(strictFunctionTypes 开启),是逆变的。对于返回值,是协变的。

let func1: (a: number) => void = (a) => console.log(a);
let func2: (a: number | string) => void = (a) => console.log(a);

func1 = func2; // 在非严格函数类型检查下合法
// func2 = func1; // 在严格函数类型检查下报错,参数类型不兼容

这里 func2 的参数类型 number | stringfunc1 的参数类型 number 更宽泛。在非严格函数类型检查下,func2 可以赋值给 func1,但在严格函数类型检查下不行。

十、模块

模块是 TypeScript 中组织代码的重要方式,它允许将代码分割成独立的单元。

(一)模块的定义与导出

  1. 导出变量、函数、类等
// utils.ts
export const PI = 3.14;
export function add(a: number, b: number) {
    return a + b;
}
export class MathUtils {
    static multiply(a: number, b: number) {
        return a * b;
    }
}
  1. 默认导出 一个模块只能有一个默认导出。
// greeting.ts
const greeting = 'Hello, World!';
export default greeting;

(二)模块的导入

  1. 导入命名导出
import { PI, add, MathUtils } from './utils';
console.log(PI);
console.log(add(2, 3));
console.log(MathUtils.multiply(4, 5));
  1. 导入默认导出
import greeting from './greeting';
console.log(greeting);
  1. 重命名导入
import { add as sum } from './utils';
console.log(sum(1, 2));
  1. 整体导入
import * as utils from './utils';
console.log(utils.PI);
console.log(utils.add(3, 4));
console.log(utils.MathUtils.multiply(6, 7));

通过以上对 TypeScript 基本语法的详细讲解,你应该对 TypeScript 有了更深入的理解,可以在实际项目中更好地运用它来提高代码的质量和可维护性。