typescript(中)--个人学习笔记

174 阅读16分钟

TS的函数

默认参数

在参数名后面加上问号y: number=100表示该参数为默认参数。如果该参数未赋值,默认为一百。

function foo(x: number, y: number=100) {
  // ...
}

可选参数

在参数名后面加上问号?表示该参数为可选参数。可选参数必须位于必选参数后面。 可选参数和默认参数不能同时用在一个参数上 回调函数里永远不要写可选参数,除非你调用该函数时用不上该参数

function foo(x: number, y?: number) {
  // ...
}

...args

在 TypeScript 中,...argsarguments 都是函数参数的特殊语法。

...args 是展开语法(Spread Syntax),它可以在函数的参数列表中使用,用于将一个数组或类数组对象展开成独立的参数。例如:

function foo(...args: number[]) {
  console.log(args); // [1, 2, 3]
}

foo(1, 2, 3);

在上面的例子中,通过 ...args 将参数列表中的所有参数展开为一个数组。

arguments 是一个特殊的函数内部对象,它包含了所有传递给函数的参数。它是一个类数组对象,可以通过索引访问参数的值。例如:

function bar() {
  console.log(arguments); // [1, 2, 3]
}

bar(1, 2, 3);

需要注意的是,arguments 是一个类数组对象,不是一个真正的数组,所以它没有数组的方法。在TS中它有专门的数据类型Iagreument,如果需要使用数组的方法,可以将 arguments 转换为一个真正的数组,例如使用 Array.from(arguments) 或者 Array.prototype.slice.call(arguments)

在 TypeScript 中,推荐使用 ...args 语法来代替 arguments,因为 ...args 更加灵活且类型安全。 TS中的函数类型可以通过以下几种方式来定义:

函数表达式

function add(x: number, y: number): number {
  return x + y;
}

箭头函数表达式

const multiply = (a: number, b: number): number => {
  return a * b;
};

函数类型变量

type Operation = (a: number, b: number) => number;

const divide: Operation = (a, b) => {
  return a / b;
};

使用函数关键字定义函数类型

type MathFunc = (x: number, y: number) => number;

let add: MathFunc = function(x: number, y: number): number {
  return x + y;
};

这表示MyFuncType是一个函数类型,接受一个number类型的参数和一个string类型的参数,返回值为void。

可选参数和默认参数

function greet(name: string, age?: number, gender: string = 'unknown'): void {
  console.log(`Hello, ${name}! You are ${age || 'unknown'} years old and your gender is ${gender}.`);
}

剩余参数

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

使用接口定义函数类型


interface MathFunc {
  (x: number, y: number): number;
}

let add: MathFunc = function(x: number, y: number): number {
  return x + y;
};

同样表示MyFuncType是一个函数类型,接受一个number类型的参数和一个string类型的参数,返回值为void。

函数属性

函数属性和类属性的差别

TS函数的属性和类的属性是两个不同的概念。

函数的属性是指在函数对象上定义的属性。在TS中,可以使用接口来定义一个函数的属性。例如:

interface Foo {
  bar: string;
}

function myFunction(): void {
  // ...
}

myFunction.bar = 'baz';

在上面的例子中,myFunction是一个函数对象,我们通过给myFunction对象添加一个bar属性来定义函数的属性。

类的属性是指在类中定义的属性。在TS中,可以使用类的成员变量来定义类的属性。例如:

class MyClass {
  myProperty: string;
}

const myObject = new MyClass();
myObject.myProperty = 'myValue';

在上面的例子中,MyClass是一个类,myProperty是类的属性。我们通过实例化一个MyClass对象,并给myProperty属性赋值来使用类的属性。

加属性的函数类型

// 定义函数类型,是将函数作为对象定义的
type Func = {
  (someArg: string): boolean
  description: string
}

// 定义函数类型
function func1(arg: string): boolean {
  console.log(arg)
  return true
}

// 给函数类型添加属性
func1.description = '这是一个函数属性'

// 将定义好的对象类型赋值给函数,发现于此同时函数的属性也被添加进去了
function dosomething(fn: Func) {
  console.log(fn.description)
}

dosomething(func1)

函数的this

在TypeScript中,this关键字用于表示当前函数的上下文对象。当一个函数作为方法被调用时,this指向调用该方法的对象。当一个函数作为普通函数调用时,this指向全局对象(在浏览器中是window对象)。

下面是一个使用this和接口的示例:

interface Person {
  name: string;
  age: number;
  sayHello: () => void;
}

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

const person: Person = {
  name: "John",
  age: 30,
  sayHello: greet
};

person.sayHello(); // 输出:Hello, my name is John and I am 30 years old.

在上面的示例中,我们定义了一个Person接口,它有name和age属性以及一个sayHello方法。然后,我们定义了一个greet函数,使用this参数将函数的上下文限制为Person类型。最后,我们创建了一个person对象,它符合Person接口的定义,将sayHello方法指向greet函数。当我们调用person对象的sayHello方法时,this指向person对象,所以在greet函数中可以访问到person对象的name和age属性。

对ts函数的个人理解,函数就类似一个类的构造函数的变形版,调用时相当于直接构造了一个类,但是毕竟不是类,导致this的不明确,所以要用是,需要自己指定this是针对谁。

换个角度说,类就是一个对象加同名的0构造函数,再new时,实际上是通过类的机制,调用里面那个构造函数

函数的重载

函数尽可能的用类型约束和泛型,不要重载

在TypeScript中,函数重载是指为同一个函数提供多个函数类型定义,以便在不同的参数类型或参数个数下可以有不同的行为。

重载的函数定义由多个函数签名组成,每个函数签名都描述了函数的参数类型和返回类型。当调用这个函数时,TypeScript会根据提供的参数类型或参数个数来选择合适的函数重载。

以下是一个示例,演示如何使用函数重载:

function foo(x: number): string;
function foo(x: string): number;
//真正发挥作用的就最后哦一个,前面的是用来提供类型提示的
function foo(x: any): any {
  if (typeof x === 'number') {
    return x.toString();
  } else if (typeof x === 'string') {
    return parseInt(x, 10);
  }
}

console.log(foo(123)); // 输出 "123"
console.log(foo('456')); // 输出 456

在上面的例子中,我们定义了两个函数签名,分别接受一个 number 类型的参数并返回一个 string 类型的结果,以及一个 string 类型的参数并返回一个 number 类型的结果。在函数体内部,我们根据参数的类型来进行不同的处理,并返回相应的结果。

注意,函数重载只存在于类型层面,在编译后的 JavaScript 代码中,并不会有多个函数定义。重载只是在编译器中进行类型检查和类型推断时使用的工具。

TS的类

类的思想就是面向对象的思想

在TypeScript中,类是一种数据结构,它用于创建具有相同属性和方法的对象。类可以包含属性(即数据)和方法(即函数),并通过实例化来创建对象。类使用关键字“class”来定义,可以通过继承来扩展其他类的属性和方法。

以下是一个使用类定义的示例:

class Person {
  name: string;
  age: number;
  

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

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

const john = new Person("John", 25);
john.sayHello(); // 输出: Hello, my name is John and I am 25 years old.

静态属性(类属性)和成员属性(实例属性)

静态属性是属于类本身的属性,而不是属于实例化对象的属性。它们使用static关键字进行声明。

成员属性是属于实例化对象的属性,每个实例化对象都有自己的成员属性。

以下是一个示例代码,展示了静态属性和成员属性的使用:

class MyClass {
  static staticProperty: number = 10; // 静态属性

  property: string; // 成员属性

  constructor(property: string) {
    this.property = property;
  }

  static staticMethod() {
    console.log("静态方法");
    console.log(MyClass.staticProperty); // 访问静态属性
  }

  memberMethod() {
    console.log("成员方法");
    console.log(this.property); // 访问成员属性
  }
}

console.log(MyClass.staticProperty); // 访问静态属性
//10
MyClass.staticMethod(); // 调用静态方法
//静态方法 10

const obj = new MyClass("成员属性");
console.log(obj.property); // 访问成员属性
//成员属性
obj.memberMethod(); // 调用成员方法
//成员方法 成员属性

在这个例子中,`staticProperty`是一个静态属性,可以通过类名直接访问,也可以在静态方法中使用。静态随着类的创建而创建

`property`是一个成员属性,只能通过实例化对象访问,也只能在成员方法中使用。
# 继承
在TypeScript中,可以使用`extends`关键字来实现类的继承。继承允许一个类继承另一个类的属性和方法,并且可以在子类中添加新的属性和方法。

下面是一个示例:

```typescript
class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} barked.`);
  }
}

let dog = new Dog("Buddy");
dog.move(10); // 输出: Buddy moved 10 meters.
dog.bark(); // 输出: Buddy barked.

在这个示例中,Dog类继承了Animal类,并通过extends关键字来指明继承关系。Dog类添加了一个新的方法bark

通过创建一个Dog类的实例,我们可以调用继承自Animal类的move方法以及Dog类自己的bark方法。

需要注意的是,子类在继承父类时可以覆盖父类的方法或属性,也可以在自己的构造器中调用父类的构造器。在TypeScript中,可以使用extends关键字来实现类的继承。继承允许一个类继承另一个类的属性和方法,并且可以在子类中添加新的属性和方法。

下面是一个示例:

class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance} meters.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} barked.`);
  }
}

let dog = new Dog("Buddy");
dog.move(10); // 输出: Buddy moved 10 meters.
dog.bark(); // 输出: Buddy barked.

在这个示例中,Dog类继承了Animal类,并通过extends关键字来指明继承关系。Dog类添加了一个新的方法bark

通过创建一个Dog类的实例,我们可以调用继承自Animal类的move方法以及Dog类自己的bark方法。

需要注意的是,子类在继承父类时可以覆盖父类的方法或属性,也可以在自己的构造器中调用父类的构造器。

super关键字

  • 当子类继承父类时,可以使用super来调用父类的构造函数、方法和属性。
  • 在构造函数中,使用super()来调用父类的构造函数,以便在子类中初始化父类的成员。
  • 在方法中,使用super.methodName()来调用父类的方法。
  • 在属性中,使用super.propertyName来访问父类的属性。

示例:

class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  eat() {
    console.log('Animal is eating.');
  }
}

class Dog extends Animal {
  breed: string;
  
  constructor(name: string, breed: string) {
    super(name); // 调用父类的构造函数
    this.breed = breed;
  }
  
  bark() {
    super.eat(); // 调用父类的eat方法
    console.log('Dog is barking.');
  }
}

const dog = new Dog('Max', 'Labrador');
dog.bark();

输出:

Animal is eating.
Dog is barking.

this关键字

  • this在TypeScript中表示当前对象或实例。
  • 在类的方法中,使用this来引用当前对象的属性和方法。
  • 当在类的方法中遇到与类属性同名的局部变量时,可以使用this来区分。

示例:

class Person {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  sayHello() {
    console.log(`Hello, my name is ${this.name}.`);
  }
  
  changeName(newName: string) {
    this.name = newName;
  }
}

const person = new Person('John');
person.sayHello(); // 输出:Hello, my name is John.

person.changeName('Jane');
person.sayHello(); // 输出:Hello, my name is Jane.

注意:在箭头函数中,this关键字的行为与普通函数不同。在箭头函数中,this指向的是定义它的上下文,而不是调用它的对象。supur和this是两个在TypeScript中使用的关键字。

接口

接口是一种用于描述对象结构的规范,它定义了对象应该具有的属性和方法。接口可以被类实现,表示类遵循该接口定义的规范。接口使用关键字“interface”来定义。

以下是一个使用接口定义的示例:

interface Animal {
  name: string;
  age: number;
  sound: string;

  makeSound(): void;
}

//在TypeScript中,implements关键字用于实现接口。接口是定义一组属性和方法的规范,而implements关键字可以使一个类满足接口的要求。
class Dog implements Animal {
  name: string;
  age: number;
  sound: string;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.sound = "Woof!";
  }

  makeSound() {
    console.log(this.sound);
  }
}

const dog = new Dog("Buddy", 3);
dog.makeSound(); // 输出: Woof!

在上面的示例中,Animal接口定义了Animal对象应该具有的属性和方法。Dog类实现了Animal接口,表示Dog类遵循Animal接口定义的规范。

抽象类和派生类

在TypeScript中,抽象类是一种被设计用于被其他类继承的基类。抽象类本身不能被实例化,只能被其他类继承。抽象类可以包含抽象方法、普通方法和属性。

抽象方法是一种在抽象类中定义的没有具体实现的方法,它只能被派生类实现。派生类需要实现抽象类中的所有抽象方法才能被实例化。抽象方法使用abstract关键字进行定义。

派生类是基于抽象类或其他类定义的类。它可以通过extends关键字继承抽象类的属性和方法,并可以重写或扩展这些属性和方法。派生类可以用来创建新的对象实例。

下面是一个使用抽象类和派生类的例子:

abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log("Moving...");
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Woof!");
  }
}

let dog = new Dog();
dog.makeSound();  // 输出: Woof!
dog.move();  // 输出: Moving...

在上面的例子中,Animal类是一个抽象类,其中包含一个抽象方法makeSound()和一个普通方法move()Dog类继承了Animal类,并实现了makeSound()方法。我们可以实例化Dog类并调用其方法。

需要注意的是,抽象类中的抽象方法必须在派生类中实现,否则会编译错误。此外,抽象类不能直接实例化,只能被继承。

类的的构造签名

在上面的示例中,我们定义了一个接口Person来表示一个人的类型,其中有两个属性name和age。然后,我们定义了一个构造签名接口PersonConstructor,它表示一个构造函数,参数name和age,返回一个Person对象。

最后,我们使用构造签名创建了一个实例PersonClass,并将其赋值给PersonConstructor类型的变量PersonClass。然后,我们使用new关键字创建出一个Person对象,并将其赋值给person变量。最后,我们打印出person对象的值。平时的类型声明针对属性,方法。这构造签名针对完整待实例化的类

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

interface PersonConstructor {
  new(name: string, age: number): Person;
}

// 使用构造签名创建一个实例
const PersonClass: PersonConstructor = class PersonClass implements Person {
  constructor(public name: string, public age: number) {}
}

const person: Person = new PersonClass("John", 25);
console.log(person); // { name: 'John', age: 25 }


对比理解

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

interface PersonConstructor {
  new(name: string, age: number): Person;
}

// 使用构造签名创建一个实例
class PersonClass implements Person {
  constructor(public name: string, public age: number) {
    this.name = name;
    this.age = age;
  }
}

function person(ctor:PersonConstructor) {
  return new ctor("John", 25);
}


const person1 = person(PersonClass);

console.log(person1); // { name: 'John', age: 25 }

联合类型和交叉类型及断言

联合类型

TS的联合类型指的是一种将多个类型组合在一起的类型。使用联合类型,我们可以将多个不同类型的值赋值给一个变量或参数。使用 | 符号将多个类型组合在一起即可创建联合类型。

例如,我们可以定义一个变量 x,它可以是 numberstring 类型的值:

let x: number | string;
x = 10; // 可以赋值为 number 类型的值
x = "hello"; // 可以赋值为 string 类型的值

在上面的例子中,x 可以接受 number 类型的值和 string 类型的值,因为它的类型被定义为 number | string

在使用联合类型的时候,我们可以使用类型保护来确定变量的具体类型,例如使用 typeofinstanceof 运算符。

function foo(x: number | string) {
  if (typeof x === "number") {
    console.log(x + 10);
  } else if (typeof x === "string") {
    console.log(x.toUpperCase());
  }
}

foo(10); // 输出 20
foo("hello"); // 输出 HELLO

联合类型在 TS 中经常用于描述某个变量或参数可以接受多种类型的值的情况,提高了代码的灵活性和可重用性。


交叉类型

交叉类型(Intersection Types)是TypeScript中的一种类型操作符,表示可以同时具有多个类型的值。使用交叉类型可以将多个类型合并为一个类型,并且只有同时满足这些类型的值才能符合交叉类型的定义。

例如,假设有两个类型A和B:

type A = {
  propA: string;
};

type B = {
  propB: number;
};

可以使用交叉类型将A和B合并为一个类型C:

type C = A & B;

// C类型的值必须具有A和B的属性
const c: C = {
  propA: "Hello",
  propB: 123
};

在上面的例子中,类型C表示同时具有A和B的属性的类型。因此,变量c的值必须具有propA和propB属性,并且分别满足string和number类型的要求。

交叉类型可以用于合并任意数量的类型,并且可以与其他类型操作符(如联合类型和类型别名)一起使用,以创建更复杂的类型定义。


类型断言

在 TypeScript 中,类型断言是一种方式,可以告诉编译器变量的类型。有两种方式可以进行类型断言。

  1. 使用尖括号语法:
let someValue: any = "hello world";
let strLength: number = (<string>someValue).length;
  1. 使用 as 关键字:
let someValue: any = "hello world";
let strLength: number = (someValue as string).length;

这两种方式是等价的,你可以选择适合你个人喜好的方式来进行类型断言。需要注意的是,类型断言仅仅是编译时的检查,并不会影响运行时的行为。在进行类型断言时要确保变量的实际类型与断言的类型是兼容的,否则可能会导致编译错误或运行时异常。

3.使用感叹号

在 TypeScript 中,"!" 和 "?" 是用于类型声明和类型检查的符号。

"!" 符号表示一个值可能为 null 或 undefined,但是在使用之前需要进行非空断言。例如:

let str: string | null = "Hello world!";
console.log(str!.length); // 非空断言,告诉编译器该值不为空

str = null;
console.log(str!.length); // 在这种情况下会抛出运行时错误


"?" 符号用于表示一个属性或方法是可选的,可能存在也可能不存在。例如:

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

const person1: Person = { name: "Alice" }; // age 属性是可选的
const person2: Person = { name: "Bob", age: 20 }; // age 属性是必需的

console.log(person1.age); // undefined
console.log(person2.age); // 20

类型别名

在TypeScript中,可以使用类型别名来为已有的类型创建一个新的名称。类型别名通过使用关键字type来定义。 type相当于抽象类 以下是一些常见的示例:

type Age = number;
type Name = string;
type Person = {
  name: Name,
  age: Age
};

const person: Person = {
  name: "John",
  age: 25
};

上面的代码中,我们通过类型别名为number类型创建了一个新的名称Age,为string类型创建了一个新的名称Name,以及为一个包含nameage属性的对象类型创建了一个新的名称Person。然后,我们使用这些类型别名来定义一个名为person的变量。

TS的接口interface

在TypeScript中,接口(Interface)是一种用于定义对象的形状(Shape)和行为的方式。接口可以包含属性和方法的定义,可以用于描述对象的结构。

下面是一些常见的用法和示例:

定义一个简单的接口

//普通的参数直接在用的时候声明类型,如果是对象这种的,直接在外面声明接口,再类型约束
interface Person {
  name: string;
  age: number;
}


let person: Person = {
  name: "Alice",
  age: 30,
  // 不能添加其他属性,否则会报错
};

接口用于函数的类型限制

interface MathFunc {
  (x: number, y: number): number;
}

let add: MathFunc = function(x: number, y: number): number {
  return x + y;
};

接口继承其他接口

interface Person {
  name: string,
  age: number,
}
interface Student extends Person {
  grade: number;
}

let student: Student = {
  name: "Bob",
  age: 20,
  grade: 80,
};

type链接符扩展

type相当于抽象类

type Person ={
  name: string,
  age: number,
}
type Student =Person& {
  grade: number;
}

let student: Student = {
  name: "Bob",
  age: 20,
  grade: 80,
};

同名接口可以重合接口

注意type是不能同时存在两个同名的。 type相当于抽象类

interface Student{
  grade: number
}
interface Student{
  name: string;
  age: number;
}
let student: Student = {
  name: "Bob",
  age: 20,
  grade: 80,
};

接口可以使用可选属性

interface Config {
  color?: string;
  width?: number;
}

function createButton(config: Config): HTMLButtonElement {
  // ...
}

let button = createButton({ color: "red" });

接口的索引签名

在 TypeScript 中,索引签名(Index Signatures)允许我们定义可以通过索引访问的属性类型。索引签名使用[]来表示。这样可以在不确定数据个数的情况下,动态定义类型。[index: string]代表index是一个string,如果在数组中可能是索引值,即为number

索引签名的语法如下:

interface SomeInterface {
  [index: string]: any;
}

这里的index可以是string也可以是number,代表了索引的类型。

索引签名可以用来定义对象或类的属性类型,使其具有动态属性。如下接口,age必须是 number类型,其他可以是string或则number:

interface User {
   age:  number,
  [key: string]: string | number;
}

const user: User = {
  name: "John",
  age: 25,
  address: "123 Street",
};

console.log(user.name); // "John"
console.log(user.age); // 25
console.log(user.address); // "123 Street"

上面的示例中,我们定义了User接口,并使用索引签名使其具有任意字符串类型的属性。这就允许了User对象有任意字符串类型的属性。

除了字符串索引签名,我们还可以使用数字索引签名来定义数组类型。例如:

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

const arr: StringArray = ["Hello", "World"];

console.log(arr[0]); // "Hello"
console.log(arr[1]); // "World"

上面的示例中,我们定义了StringArray接口,并使用了数字索引签名来定义数组类型。这样,我们就可以通过索引访问数组中的元素。

TS 支持 string 和 number 索引类型,可以同时使用两种类型的索引,但 number 索引的值(value)类型必须是 string 索引值(value)类型的子类型。 这是因为 JavaScript 语言规定:对象的键名一律为字符串。使用索引获取对象时,非字符串索引会被自动转为字符串。因为数组本身是对象,所以数组的索引(下标)实际上是字符串。

接口的只读修饰符

在TypeScript的接口中,可以使用readonly关键字来创建只读属性。只读属性表示只能在对象创建时被赋值,不能被修改。

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

let person: Person = {
  name: "John",
  age: 30
};

console.log(person.name); // "John"
console.log(person.age); // 30

// 以下代码会报错,因为name和age是只读属性
person.name = "Tom";
person.age = 40;

在上面的例子中,name和age属性都被声明为只读属性。当我们创建了Person对象后,就不能修改name和age属性的值了。如果试图修改只读属性的值,TypeScript编译器会报错。

需要注意的是,只读属性只能在对象创建时被赋值,一旦被赋值后就不能再被改变。这意味着,只读属性必须在对象创建时就有一个初始值。如果尝试在对象创建后修改只读属性的值,编译器将会报错。

readonly vs const

最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用readonly

const只是不能改栈中地址,但是如果是数组这种的引用类型,虽然不能改变数组,但是依旧可以改变数组中的值

TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

上面代码的最后一行,可以看到就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:

a = ro as number[];

类型缩小

在 TypeScript 中,有几种方法可以将类型缩小为更具体的类型,其中包括:

类型断言

使用 as 关键字将变量的类型指定为更具体的类型

let value: unknown = 5;
let num: number = (value as number) + 10;

条件判断:

包括==,===,!=,!===,<,>,a?b:c

let num: number = 5;
let isPositive: boolean = num > 0;

typeof 操作符

使用 typeof 操作符获取变量的类型并进行判断

判断的有:stringnumber,booleam,undefined,object,function,bigint,symbol
function getLength(value: string | number) {
  if (typeof value === 'string') {
    return value.length;
  }
  return value;
}

instanceof 操作符

使用 instanceof 操作符检查一个对象是否属于某个类的实例,例如:

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

function greet(person: Person | string) {
  if (person instanceof Person) {
    return `Hello, ${person.name}!`;
  }
  return `Hello, ${person}!`;
}

in 操作符

使用 in 操作符判断一个属性是否存在于一个对象中

interface Dog {
  name: string;
  breed: string;
}
interface Cat {
  name: string;
  breed: string;
}

function print(animal: Dog | Cat) {
  if ('breed' in dog) {
    console.log(`This dog is ${dog.name}.`);
  } else {
     console.log(`This dog is ${cat.name}.`);
  }
}

这些方法都可以根据不同的条件缩小一个变量的类型,使其更具体。

is类型谓词

在TypeScript中,is是一种类型谓词(type predicate)语法,用于在运行时判断一个值的类型是否是某个给定类型。

语法:

parameterName is Type

示例:

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function doSomething(value: unknown) {
  if (isString(value)) {
    console.log(value.toLowerCase());
  } else {
    console.log('Value is not a string');
  }
}

doSomething('Hello'); // 输出:hello
doSomething(123); // 输出:Value is not a string

在上面的示例中,isString函数判断一个值是否是字符串类型。如果判断为真,则value的类型被缩小为string类型,因此在if语句中可以安全地调用字符串方法toLowerCase。如果判断为假,则else部分会被执行。

通过使用is语法,我们可以在编译时进行类型检查,使代码更加健壮和可靠。

联合类型简单的案例

不理想的示例 这个案例看上去没有问题,但是radius和sideLength,是可选的,虽然在函数中用!强制断言了,但是用户还是可能没给。随之就会出错。

  type Shape = {
  kind: 'circle'|'square';
  radius?: number
  sideLength?: number
};

// 求面积
function getArea(shape: Shape): number {
  
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius! ** 2;
  } else {
    return shape.sideLength! ** 2;
  }
}
// 调用
getArea({ kind: 'circle', radius: 10 });

正确示例

// 定义一个圆形的type
type Circle = {
  kind: 'circle'
  radius: number;
};
// 定义一个正方形的type
type Square = {
  kind: 'square'
  sideLength: number;
}
// 求面积
type Shape = Circle | Square
// 求面积
function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLength ** 2
    default:
    //这是如果输入一个三角形,因为超出程序设定,所以就会报never的错
      {
      const _exhaustiveCheck: never = shape
      throw new Error(`不能使用该类型: ${JSON.stringify(_exhaustiveCheck)}`)
      }
  }
}
// 使用函数
const circle: Shape = { kind: 'circle', radius: 5 }
const square: Shape = { kind: 'square', sideLength: 4 }
console.log(getArea(circle)) // 输出 78.53981633974483
console.log(getArea(square)) // 输出 16

布尔真值缩小

条件判断:

let num: number = 5;
let isPositive: boolean = num > 0;

在这个例子中,通过判断num是否大于0,可以将isPositive缩小为truefalse

布尔赋值

boolean("hello")

双叹号

第一个叹号会对字符串取反,将其转换成了布尔类型,第二个叹号再取反,将值再转换回去

!!"world"