关于 TypeScript 和 javaScript 的主要对比

一、TypeScript 是什么

        TypeScript 是 JavaScript 的一个超集,它可以编译为纯 JavaScript,并且可以在任何浏览器、操作系统和 Node.js 环境中运行。TypeScript 扩展了 JavaScript,添加了静态类型和面向对象编程的特性,以提供更好的开发工具支持和代码可维护性。

二、ts 和 js 的对比

三、ts 声明类型的两种方式(类型注解和类型推断)

1、类型注解

    (1) 定义

        类型注解是一种在代码中显式指定变量或函数的类型的方式。它使用特定的语法来告诉编译器或开发人员该变量或函数应该具有的类型。

       (2) 使用

let isDone: boolean = false; // 指定 isDone 为布尔类型
let binaryLiteral: number = 0b1010; // 指定 binaryLiteral 为 number 类型
let name: string = "bob";
let name: string = `Gene`; // 指定 name 为 string 类型
let list: number[] = [1, 2, 3];  // 指定 name 为 数组 类型 方式一
let list: Array = [1, 2, 3];  // 指定 name 为 数组 类型 方式二
let x: [string, number]; // 指定 x 为元组类型
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
enum Color {Red, Green, Blue}
let c: Color = Color.Green; // 指定枚举类型
let notSure: any = 4; // 指定 any 类型
function warnUser(): void { // 指定 void 类型
    console.log("This is my warning message");
}
// 返回never的函数必须存在无法达到的终点
function error(message: string): never { // 指定 never 类型
    throw new Error(message);
}

2、类型推断

        (1) 定义

       在编程语言中,类型推断是指根据代码上下文和语法规则,自动推断出表达式的数据类型。这种机制可以使程序员在编写代码时无需显式地声明每个变量的类型,从而提高编程效率和代码可读性。

        (2) 使用

// 变量类型推断
let a = 123; // 推断 a 的类型为 number
let b = 'abc'; // 推断 b 的类型为 string
// 函数返回值类型推断
function add(a: number, b: number) {
  return a + b; // 推断返回值类型为 number
}
// 参数类型推断
function greet(name: string) {
  console.log(`Hello, ${name}!`); // 参数 name 的类型被推断为 string
}
// 对象属性类型推断
const person = {
  name: 'John', // 推断 name 的类型为 string
  age: 25 // 推断 age 的类型为 number
};
// 类型守卫和联合类型推断
unction printId(id: number | string) {
  if (typeof id === 'number') {
    console.log(`ID: ${id}`); // 为数字
  } else {
    console.log(`ID: '${id}'`); // 为字符
  }
}

四、接口

  (1) 定义

     TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

 (2) 接口属性值

// 必选属性
interface Person {
  name: string;
  age: number;
}
const person1: Person = { name: "Alice", age: 20 }; // OK
const person2: Person = { name: "Bob" }; // Error: 缺少必选属性 'age'
// 可选属性
interface Config {
  width?: number;
  height?: number;
}
const config1: Config = { width: 100 }; // OK
const config2: Config = {}; // OK
const config3: Config = { width: "100" }; // Error: 类型不匹配
// 只读属性
interface Point {
  readonly x: number;
  readonly y: number;
}
const point: Point = { x: 10, y: 20 };
point.x = 20; // Error: 只读属性不能被修改
// 函数类型属性
interface Calculator {
  add(x: number, y: number): number;
  subtract(x: number, y: number): number;
}
const calculator: Calculator = {
  add(x, y) {
    return x + y;
  },
  subtract(x, y) {
    return x - y;
  },
};
// 索引签名
interface Dictionary {
  [key: string]: T;
}
const dict1: Dictionary = { a: 1, b: 2 }; // OK
const dict2: Dictionary = { a: "hello", b: "world" }; // OK
const dict3: Dictionary = { a: 1, b: "2" }; // Error: 类型不匹配

(3)接口的作用和应用场景

主要作用:

  1. 定义对象的类型:接口可以用来定义对象的类型,描述对象应该具有的属性和方法。通过接口,可以明确指定对象应该具有哪些属性、属性的类型以及方法的参数和返回值类型。这样可以提高代码的可读性和可维护性,同时也可以在编译时进行类型检查,减少错误

  2. 实现类的约束:接口可以被类实现,用来强制要求类实例具有接口中定义的属性和方法。通过接口的约束,可以确保类按照接口的要求进行实现,从而实现接口和实现类的解耦,增强代码的灵活性和可扩展性。

  3. 函数类型定义:接口可以用来定义函数的类型,指定函数的参数和返回值类型。通过接口的约束,可以在编译时进行类型检查,确保函数的调用符合接口的约束,避免参数类型不匹配或缺少必要参数等错误。

  4. 接口的继承:接口可以通过继承其他接口,从而扩展和组合接口的功能。这样可以实现接口的复用和模块化,减少重复定义,提高代码的可维护性。

代码演示

定义对象类型
// 定义一个 Person 接口,表示一个人的属性
interface Person {
  name: string;
  age: number;
}
// 创建一个 Person 对象,符合 Person 接口的要求
const person: Person = {
  name: "Alice",
  age: 30,
};
实现类的约束
// 定义一个 Animal 接口,表示一个动物的属性和方法
interface Animal {
  name: string;
  speak(): void;
}
// 创建一个 Dog 类,实现 Animal 接口的要求
class Dog implements Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  // 实现 speak 方法,符合 Animal 接口的要求
  speak() {
    console.log("Woof woof!");
  }
}
// 创建一个 Dog 对象,符合 Animal 接口的要求
const dog: Animal = new Dog("Buddy");
dog.speak(); // 输出 "Woof woof!"
函数类型的定义
// 定义一个 MathOperation 接口,表示一个数学运算的函数类型
interface MathOperation {
  (x: number, y: number): number;
}
// 创建一个 add 函数,符合 MathOperation 接口的要求
const add: MathOperation = (x, y) => {
  return x + y;
};
console.log(add(2, 3)); // 输出 5
接口的继承
// 定义一个 Animal 接口,表示一个动物的属性和方法
interface Animal {
  name: string;
  speak(): void;
}
// 定义一个 Mammal 接口,继承 Animal 接口,表示一个哺乳动物的属性和方法
interface Mammal extends Animal {
  breed(): void;
}
// 创建一个 Dog 类,实现 Mammal 接口的要求
class Dog implements Mammal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  // 实现 speak 方法,符合 Animal 接口的要求
  speak() {
    console.log("Woof woof!");
  }
  // 实现 breed 方法,符合 Mammal 接口的要求
  breed() {
    console.log("Giving birth to puppies.");
  }
}
// 创建一个 Dog 对象,符合 Mammal 接口的要求
const dog: Mammal = new Dog("Buddy");
dog.speak(); // 输出 "Woof woof!"
dog.breed(); // 输出 "Giving birth to puppies."

五、泛型

1、定义

        TypeScript 中的泛型(Generics)可以在不指定具体类型的情况下,让函数或类适用于多种类型,提高代码的灵活性和复用性。泛型可以用于函数、类和接口等地方。

2、 作用

  1. 类型安全:使用泛型可以在编译时进行类型检查,确保类型的一致性和正确性。这可以减少运行时的类型错误,提高代码的健壮性和可靠性。

  2. 代码复用:泛型使得代码可以适用于多种类型的数据,避免了重复编写相似逻辑的问题。通过将类型参数化,可以在不同的上下文中重用代码,提高开发效率。

  3. 抽象数据类型:泛型可以用来定义抽象的数据类型,使代码更加通用和可扩展。通过将类型作为参数来处理数据,可以实现更高层次的抽象和封装。

  4. 提供灵活性:泛型可以用于处理各种类型的数据,包括基本类型、自定义类型和其他泛型。它们允许开发者根据具体需求进行定制和扩展,提供了更大的灵活性和自由度。

3、类别和适用场景

        泛型函数:适用于需要处理不同类型参数和返回值的函数。

function identity(arg: T): T {
  return arg;
}
const result = identity(10);
console.log(result); // 输出: 10
const value = identity("Hello");
console.log(value); // 输出: Hello

        泛型类:适用于需要在类中处理不同类型属性和方法的情况。

class GenericClass {
  private value: T;
  constructor(value: T) {
    this.value = value;
  }
  public getValue(): T {
    return this.value;
  }
}
const numberInstance = new GenericClass(10);
console.log(numberInstance.getValue()); // 输出: 10
const stringInstance = new GenericClass("Hello");
console.log(stringInstance.getValue()); // 输出: Hello

        泛型接口:适用于定义需要处理不同类型属性和方法的接口。

interface GenericInterface {
  value: T;
  getValue(): T;
}
const numberObj: GenericInterface = {
  value: 10,
  getValue() {
    return this.value;
  }
};
console.log(numberObj.getValue()); // 输出: 10
const stringObj: GenericInterface = {
  value: "Hello",
  getValue() {
    return this.value;
  }
};
console.log(stringObj.getValue()); // 输出: Hello

        泛型约束:适用于需要对泛型参数进行类型约束的情况。

interface Lengthwise {
  length: number;
}
function loggingIdentity(arg: T): T {
  console.log(`Length: ${arg.length}`);
  return arg;
}
const obj = { length: 5, value: "Hello" };
loggingIdentity(obj); // 输出: Length: 5