☰
TS学习(2)
2026/10/26 18:16:36 网站建设 项目流程

🔥keyipatience:个人主页

🎬作者简介:C/C++后端开发学习者

🌟专栏传送门:《c++》《linux》《c++高阶数据结构》《c++数据结构与算法》《前端学习》

⭐️patience is key in life

枚举 enum

枚举(enum):定义一组命名常量,限定一个值只能是里面其中之一,作用等价于字面量类型 + 联合类型。通过 .方式访问

// 定义枚举 enum Direction { Up, Down, Left, Right } // 枚举直接当作类型注解 function changeDirection(direction: Direction) { console.log(direction) }

默认数字枚举

enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 } changeDirection(Direction.Up) // 传枚举成员

也可以手动设置起始值:

enum Direction { Up = 10, Down, // 11 Left, // 12 Right // 13 }

和「字面量联合类型」对比

// 字面量联合写法 type Direction = 'Up' | 'Down' | 'Left' | 'Right'

字符串枚举(常用)

enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT" } changeDirection(Direction.Up)

枚举的特点和原理

enum 是 TS 里少数编译后还保留 JS 代码的特性普通type、接口这些只是类型,编译成 JS 直接删掉; 枚举不只是类型,它编译后会生成真实 JS 对象,运行时也存在。

// TS源码:字符串枚举 enum Direction { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT' }

编译出来的 JS

var Direction; (function (Direction) { Direction["Up"] = "UP"; Direction["Down"] = "DOWN"; Direction["Left"] = "LEFT"; Direction["Right"] = "RIGHT"; })(Direction || (Direction = {}));

对比:枚举 vs 字面量联合类型

// 字面量联合,编译后完全消失,运行时不存在 type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

enum:编译后有 JS 代码,运行时可用;可以动态遍历、取值;但是代码体积更大

type 联合:编译后直接抹除,无运行时代码;更轻量简洁

日常开发优先推荐字面量联合类型

any 类型

any:任意类型。标记为any的变量,可以做任意操作,TS 不再做类型检查,相当于关闭类型保护

let obj: any = { x: 0 } obj.bar = 100 // ✅ 允许随便新增属性 obj() // ✅ 允许把对象当成函数调用(运行时会报错,但TS不提示) const n: number = obj // ✅ 允许直接赋值给number类型,TS不拦截

重点原则

尽量不要用 any!TS 的核心价值就是类型校验,any 会直接废掉这个能力。 只有一种场景临时用:类型极其复杂、临时跳过类型校验,优先想别的方案代替。

隐式 any(自动变成 any 的两种情况)

1.只声明变量,不写类型注解、也不给初始值

let a; // 隐式 any ❌ 不推荐

2.函数参数不写类型注解

function fn(p) { // p 隐式 any ❌ }

规范:这两种情况都要手动补上类型。

和 unknown 的简单区分

  • any:随便读、随便调用、随便赋值给别的类型,无限制
  • unknown:也是未知类型,但不能随便操作,必须先做类型判断,更安全,是 any 的优选替代方案

typeof(类型查询)

区分两个完全不一样的typeof:

1.JS 运行时 typeof:在代码执行时,得到字符串("string"/"number")

console.log(typeof "Hello world") // 运行输出:string const num = 99 type NumType = typeof num // NumType 就是 number const str = "abc" type StrType = typeof str // string

2.TS 类型上下文的 typeof:编译期,拿到变量 / 属性的类型,用来写类型注解

示例对比

let p = { x: 1, y: 2 } // 写法1:手动写出对象类型,即point的类型是XXX function formatPoint(point: { x: number; y: number }) {} // 写法2:TS typeof,直接拿到 p 的类型,简化代码 ✅ function formatPoint(point: typeof p) {}

typeof p等价于{ x: number; y: number }

class的基本使用

TS 兼容 ES6 的class,还额外增加类型注解、访问修饰符(public/private/protected)等能力。

class Person {} // 写法1:自动类型推断,p 的类型是 Person(推荐) const p = new Person() // 写法2:手动写类型注解 Person const p: Person = new Person()
  1. class本身可以直接当作类型使用在 TS 里,类有双重身份:
    • 运行时:是构造函数(编译后 JS 真实存在,可以new)
    • 类型层面:代表这个类实例的类型,所以写p:Person
  2. 写new Person()的时候,TS 会自动推断出变量类型是Person,所以类型注解: Person可以省略。

实例属性初始化

class Person { // 1. 只写类型注解,没有初始值 age: number // 2. 直接赋值初始值,TS自动推断类型,可以省略类型注解 gender = '男' // 等价于:gender: string = '男' }

构造函数 constructor

class Person { age: number gender: string constructor(age: number, gender: string) { this.age = age this.gender = gender } }

1.必须先在类里声明实例成员(age: number、gender: string)声明之后,才能在构造函数里用this.age/this.gender赋值、访问。

JS 可以直接this.xxx不用提前声明;TS 要求要先声明。

2.constructor 的特点(考点)

  • 参数必须加类型注解,不然参数隐式变成any;
  • 构造函数不要写返回值类型:它自动返回当前类的实例,不能写: Person。

实例实现方法

class Point { x = 10 y = 10 scale(n: number): void { this.x *= n this.y *= n } } const p = new Point() p.scale(2)

类继承:extends和implements

  • extends:继承另一个类(拿到父类的属性、方法,JS、TS 都能用)
  • implements:实现接口(只遵守接口的类型约定,不会继承任何代码,TS 独有,JS 没有)

extends继承类:

class Animal { move() { console.log('Moving along!') } } // Dog 继承 Animal class Dog extends Animal { bark() { console.log('汪!') } } const dog = new Dog() dog.move() // 来自父类Animal dog.bark() // 自己的方法

implements 实现接口

interface Singable { sing(): void } class Person implements Singable { sing() { console.log('你是我的小呀小苹果儿') } } //一个类可以同时实现多个接口 interface Singable { sing():void } interface Runable { run():void } class Person implements Singable, Runable { sing(){} run(){} } 也可以一边继承父类、一边实现接口: class Student extends Person implements Singable {}
  1. interface(接口)只定义规范,定义一个标准:需要有哪些方法、属性,没有实现代码。
  2. implements写在类上,表示这个类遵守这个接口的约定,遵守该接口规则。
  3. 强制要求:类里面必须实现接口声明的全部成员,少一个 TS 就报错。

访问修饰符 public /private/protected

一段代码直接看出区别:

class Person { // public:公开,任何地方都能访问(默认修饰符,可以不写) public name: string // private:私有,**只能在当前类内部**访问,子类、实例都访问不到 private age: number // protected:受保护,当前类 + 子类内部可以访问,外部实例不能访问 protected gender: string constructor(name: string, age: number, gender: string) { this.name = name this.age = age this.gender = gender } say() { console.log(this.name, this.age, this.gender) // 类内部:三个全都能访问 } } class Student extends Person { study() { console.log(this.name) // ✅ public 可以 console.log(this.gender) // ✅ protected 子类内部可以 // console.log(this.age) // ❌ private,子类拿不到 } } const p = new Person("张三", 18, "男") console.log(p.name) // ✅ public,外部可以 // console.log(p.age) // ❌ private,外部报错 // console.log(p.gender) // ❌ protected,外部实例访问报错

构造函数参数上可以直接加修饰符语法糖

class Person { // 直接自动生成属性,省去单独声明 constructor(public name:string, private age:number) {} }

展开后的完整等价代码:

class Person { public name: string private age: number constructor(name: string, age: number) { this.name = name this.age = age } }

readonly 只读修饰符

作用:属性只能初始化一次,之后不能修改,只能读。

class Person { readonly id: number//也可以直接在这里赋值 name: string constructor(id: number, name: string) { this.id = id // ✅ 构造函数里可以赋值(唯一可以修改的地方) this.name = name } } const p = new Person(1001, "小明") console.log(p.id) // ✅ 可以读取 // p.id = 2002 // ❌ 报错!readonly,实例创建后禁止修改 p.name = "小红" // ✅ 普通属性正常修改

还可以搭配语法糖(构造参数简写)

class Person { constructor(readonly id: number, public name: string) {} }

readonly只限制重新赋值,如果属性是对象 / 数组,里面的内容依然可以改!

class Test { readonly arr: number[] = [1,2,3] } const t = new Test() t.arr.push(4) // ✅ 合法!只是不能 t.arr = [9,9,9]

类型兼容性

1.结构化类型系统(Structural,TS、Python)

看形状(有哪些属性、方法),不看名字。长得一样,就兼容。 俗称鸭子类型:看起来像鸭子、叫起来像鸭子 → 就是鸭子。

class Point { x: number; y: number } class Point2D { x: number; y: number } const p: Point = new Point2D() // ✅ 不报错!

虽然是两个完全独立的类,名字不一样,但成员一模一样,结构匹配,赋值合法。

2.标明类型系统(Nominal,Java / C# / C++)

看类型名字本身,必须是同一个类或者有继承关系才可以赋值。 上面这段代码放到 Java 直接编译报错,Point和Point2D就是两个无关类型。

对象的类容兼容性

多的可以赋值给少的,反过来不行

class Point { x: number; y: number } // 少:x、y class Point3D { x: number; y: number; z: number } // 多:x、y、z const p: Point = new Point3D() // ✅ 合法 // const p3: Point3D = new Point() // ❌ 报错!Point 缺 z

接口的类型兼容性

规则:和 class 对象的兼容规则一模一样:看结构,不看名字;多的可以赋值给少的,反过来不行

interface Point { x: number; y: number } interface Point2D { x: number; y: number } let p1: Point let p2: Point2D = p1 // ✅ 结构完全一样,可以赋值 interface Point3D { x: number; y: number; z: number } let p3: Point3D p2 = p3 // ✅ Point3D 有 x,y,满足 Point2D 的要求,多了z没关系 // p3 = p2 // ❌ 报错,p2 缺少 z

interface 和 class 互相之间也可以兼容!

class Point3D { x: number; y: number; z: number } let p3: Point2D = new Point3D() // ✅ 合法

函数兼容性

参数个数角度:

和前面反过来,少的可以给多的,多的不能给少的

type F1 = (a: number) => void type F2 = (a: number, b: number) => void let f1: F1 let f2: F2 = f1 // ✅ 合法!参数少的 f1 赋值给参数多的 f2

参数类型角度

相同位置的参数:原始类型要一模一样;

type F1 = (a: number) => string type F2 = (a: number) => string let f1: F1 let f2: F2 = f1 // ✅ 可以,对应位置参数类型相同 type F3 = (a: string) => string // f2 = f3 ❌,a一个是number、一个是string,原始类型不匹配

如果参数是对象(走对象兼容)多的给少的!!

interface P {x:number} interface P3 {x:number; y:number} type FA = (p: P)=>void//参数是p,p的类型是P即对象 type FB = (p: P3)=>void let fa: FA let fb: FB fa = fb // ✅ P3包含P的属性,对象兼容

返回值类型角度

原始类型就简单:返回值类型必须相同

type Fa = () => string type Fb = () => string let fa: Fa let fb: Fb = fa // ✅ type Fc = () => number // fa = Fc ❌ string 和 number 不兼容

同样如果参数是对象,多的给少的

type F1 = () => {x: number}//返回一个对象,对象必须有 x(数字) type F2 = () => {x: number; y: number}//返回对象,同时有 x 和 y let f1: F1 let f2: F2 f1 = f2 // ✅ OK // f2 = f1 // ❌ 报错,f1返回值少y

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询