一、类型声明
TypeScript
// 变量类型声明
let name: string = "张三";
let age: number = 25;
let isStudent: boolean = true;
let hobbies: string[] = ["篮球", "阅读", "编程"];
let scores: number[] = [95, 88, 92];
// 对象类型声明
let person: {
name: string;
age: number;
email?: string; // 可选属性
} = {
name: "李四",
age: 30
};
// 数组类型声明(多种写法)
let numbers: Array<number> = [1, 2, 3];
let matrix: number[][] = [[1, 2], [3, 4]];
// 元组类型(固定长度和类型)
let tuple: [string, number] = ["王五", 28];
// 联合类型
let id: string | number;
id = "ABC123";
id = 123;
// 函数参数和返回值类型声明
function add(x: number, y: number): number {
return x + y;
}
// 箭头函数类型声明
const multiply = (a: number, b: number): number => a * b;
// 接口声明
interface User {
id: number;
name: string;
age?: number;
}
let user: User = {
id: 1,
name: "小明"
};
// 类型别名
type Point = {
x: number;
y: number;
};
const point: Point = { x: 10, y: 20 };
// 枚举声明
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
let move: Direction = Direction.Up;
二、类型推断
TypeScript
// ========== 基础类型推断 ==========
// 变量初始化推断
let name = "张三"; // TypeScript 推断为 string
let age = 25; // 推断为 number
let isActive = true; // 推断为 boolean
let scores = [90, 85, 95]; // 推断为 number[]
// 自动推断示例
let items = ["苹果", "香蕉", "橙子"]; // 推断为 string[]
items.push("葡萄"); // ✓ 正确
// items.push(123); // ✗ 错误:类型"number"的参数不能赋给类型"string"的参数
// ========== 最佳通用类型推断 ==========
// 混合类型数组推断
let mixedArray = [1, "hello", true]; // 推断为 (string | number | boolean)[]
// ========== 上下文类型推断 ==========
// 事件处理函数推断
document.addEventListener("click", function(event) {
console.log(event.clientX, event.clientY); // event 被推断为 MouseEvent
});
// ========== 函数返回类型推断 ==========
// 函数返回值推断
function add(a: number, b: number) {
return a + b; // TypeScript 推断返回值为 number
}
const result = add(5, 3); // result 被推断为 number
function createUser(name: string, age: number) {
return {
name, // 推断返回类型为 { name: string; age: number }
age
};
}
const user = createUser("小红", 25); // user 被推断为 { name: string; age: number }
// ========== 对象属性推断 ==========
const employee = {
name: "张三",
age: 30,
department: "技术部",
skills: ["TypeScript", "React", "Node.js"] // 推断为 string[]
};
// TypeScript 推断 employee 的类型为:
// {
// name: string;
// age: number;
// department: string;
// skills: string[];
// }
// ========== 解构推断 ==========
const company = {
companyName: "ABC科技",
location: "北京",
employees: 500
};
const { companyName, location } = company;
// companyName 推断为 string
// location 推断为 string
// ========== 数组方法推断 ==========
const numbers = [1, 2, 3, 4, 5];
// map 方法推断
const doubled = numbers.map(n => n * 2); // 推断为 number[]
// filter 方法推断
const evens = numbers.filter(n => n % 2 === 0); // 推断为 number[]
// ========== 条件语句中的推断 ==========
let value: string | number = "hello";
if (typeof value === "string") {
console.log(value.toUpperCase()); // 在此块中,value 被推断为 string
} else {
console.log(value.toFixed(2)); // 在此块中,value 被推断为 number
}
// ========== 类型断言与推断 ==========
// 有时需要帮助 TypeScript 进行推断
const input = document.getElementById("username");
// 如果没有类型断言,input 被推断为 HTMLElement | null
if (input) {
// 使用类型断言帮助推断
(input as HTMLInputElement).value = "张三";
}
// ========== 更复杂的推断案例 ==========
// 1. 数组映射推断
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: "笔记本", price: 5000 },
{ id: 2, name: "鼠标", price: 100 }
];
// 自动推断为 number[]
const productPrices = products.map(product => product.price);
// 2. 异步函数推断
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json(); // 推断为 any
return data;
}
// 3. 类中的推断
class Calculator {
private currentValue = 0; // 推断为 number
add(num: number) {
this.currentValue += num; // TypeScript 知道这是数字运算
return this.currentValue; // 推断返回 number
}
}
// ========== 实际应用示例 ==========
// 案例:处理用户数据
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "张三", email: "zhangsan@example.com" },
{ id: 2, name: "李四", email: "lisi@example.com" }
];
// TypeScript 能推断出:
// - user 的类型是 User
// - userNames 的类型是 string[]
const userNames = users.map(user => user.name);
// TypeScript 能推断出:
// - activeUsers 的类型是 User[]
// - 回调参数 user 的类型是 User
const activeUsers = users.filter(user => user.id > 0);
// 案例:配置对象推断
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retry: true,
headers: {
"Content-Type": "application/json"
}
};
// config 被完整推断,无需显式类型声明
console.log(config.headers["Content-Type"]); // 正确推断
三、类型总览
(1)Javascript 中的数据类型
string、number、boolean、null、undefined、bigint、symbol、object
object 包含:Array、Function、Date、Error
(2)TypeScript 中的数据类型
上述所有的 JavaScript 类型
六个新类型:any、unknown、never、void、tuple、enum
两个用于自定义类型的方式:type、interface
四、常用类型与语法
(1) any
any 是 TypeScript 中最灵活的类型,它禁用了类型检查。
TypeScript
// 1. 基本用法
let value: any;
value = 42; // ✓ 可以赋值为数字
console.log(value); // 42
value = "hello"; // ✓ 可以赋值为字符串
console.log(value.toUpperCase()); // HELLO
value = true; // ✓ 可以赋值为布尔值
value = [1, 2, 3]; // ✓ 可以赋值为数组
value = { name: "张三" }; // ✓ 可以赋值为对象
// 2. any 绕过了类型检查
let num: number = 10;
let anyValue: any = "我是一个字符串";
num = anyValue; // ✓ 不会报错,但运行时可能出错
console.log(num.toFixed(2)); // 运行时错误!
// 3. 函数中的 any
function processData(data: any) {
// 在这里可以对 data 做任何操作
console.log(data.name);
console.log(data.age);
return data;
}
// 可以传入任何类型的参数
processData("字符串");
processData(123);
processData({ name: "张三" });
processData([1, 2, 3]);
// 4. any 数组
let mixedArray: any[] = [1, "hello", true, { name: "test" }];
mixedArray.push(100); // ✓
mixedArray.push("world"); // ✓
// 5. 隐式的 any
// 当没有类型注解且 TypeScript 无法推断类型时,会隐式推断为 any
let implicitAny; // 类型为 any
implicitAny = 10;
implicitAny = "string";
// 开启 strict 模式时,这会被禁止
为什么不推荐使用 any?
TypeScript
// 使用 any 会失去 TypeScript 的优势
let userId: any = "user123";
// TypeScript 不会报错,但这是错误的用法
userId.toFixed(2); // ✗ 运行时错误:字符串没有 toFixed 方法
// 对比:使用具体类型
let userId2: string = "user123";
// userId2.toFixed(2); // ✓ TypeScript 会立即报错,提示你错误
// 更好的做法:使用联合类型
let userId3: string | number = "user123";
if (typeof userId3 === "number") {
userId3.toFixed(2); // ✓ 安全,有类型保护
}
何时使用
any?迁移 JavaScript 项目到 TypeScript
处理第三方库没有类型定义的情况
暂时绕过复杂的类型问题
TypeScript
// 实际例子:处理未知的 API 响应
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data: any = await response.json(); // 暂时使用 any
return data;
}
// 稍后可以优化为具体类型
interface ApiResponse {
id: number;
name: string;
// ...
}
async function fetchDataBetter() {
const response = await fetch('https://api.example.com/data');
const data: ApiResponse = await response.json();
return data;
}
替代 any 的方案
TypeScript
// 1. unknown - 比 any 更安全
let value: unknown;
value = 10;
value = "hello";
// 需要类型检查后才能使用
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✓ 安全
}
// 2. 类型断言
let someValue: any = "hello";
let strLength: number = (someValue as string).length;
// 3. 泛型 - 保持类型安全
function identity<T>(arg: T): T {
return arg;
}
// 4. 类型保护
function isString(value: any): value is string {
return typeof value === "string";
}
any是 TypeScript 的"逃生舱",可以暂时绕过类型检查,但应该尽量避免使用,因为会失去类型安全的优势。
(2) unknown
1.可以理解为一个类型安全的any,适用于:不确定数据的具体类型
2.会强制开发者在使用之前进行类型检查从而提供更强的类型安全性
TypeScript
// 设置 a 的类型为 unknown
let a : unknown
a = 99
a = false
a = 'hello'
console.log(a)
let x : string
// 第一种:加类型判断
if ( typeof a === 'string'){
x = a
}
// 第二种:加断言
x = a as string
// 第三种:加断言
x = <string>a
3.读取 any 类型数的任何属性都不会报错,而 unknown 正好相反
TypeScript
let str1: string
str1 = 'hello'
str1.toUpperCase() // 无警告
let str2: any
str2 = 'hello'
str2.toUpperCase() // 无警告
let str3: unknown
str3 = 'hello'
str3.toUpperCase() // 警告:'str3' 的类型为 '未知'
// 使用断言强制指定 str3 的类型为 string
(str3 as string).toUpperCase() // 无警告
(3) never
含义:任何值都不是,简言之就是不能有值,undefined、null、''、0都不行
1.几乎不用 never 去直接限制变量,因为没有意义,
- never 一般是 TypeScript 主动推断出来的
TypeScript
// 指定 a 的类型为 never, 那就意味着 a 以后不能存任何的数据了
let a: never
// 以下对 a 的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null
- never 一般是 TypeScript 主动推断出来的
TypeScript
// 指定 a 的类型为 string
let a: string
// 给 a 设置一个值
a = 'hello'
if (typeof a === 'string'){
console.log(a.toUpperCase())
} else {
console.log(a) // TypeScript 会推断出此处的 a 是 never,因为没有任何一个值符合此处的逻辑
}
- never 也可用于限制函数的返回值
TypeScript
// 限制 throwError 函数不需要有任何返回值,任何值都不行,像 undefined、null 都不行
function throwError(str: string): never {
throw new Error ('程序异常退出' + str)
}
(4) void
通常用于函数返回值声明,含义:【函数不返回任何值,调用者也不应该依赖其返回值进行任何操作】
TypeScript
function logMessage(msg:string):void{
console.log(msg)
}
logMessage('Hello')
注意:编码者没有编写 return 去指定函数的返回值,所以 logMessage 函数是没有显示返回值的,但会有一个隐式返回值,就是 undefined;即:虽然函数返回类型为 void,但也是可以接受 undefined 的,简单记:undefined 是 void 可以接受的一种 '空'。
- 以下写法均符合规范
TypeScript
// 无警告
function logMessage(msg:string):void{
console.log(msg)
}
// 无警告
function logMessage(msg:string):void{
console.log(msg)
return
}
// 无警告
function logMessage(msg:string):void{
console.log(msg)
return undefined
}
理解 void 和 undefined
void 是一个广泛的概念,用来表达'空', 而 undefined 则是这种空的具体实现之一。
因此可以说 undefined 是 void 能接受的 '空' 状态的一种具体形式。
换句话说:void 包含 undefined,但 void 表达的语义超越了单纯的 undefined,它是一种意图上的约定,而不仅仅是特定的限制。
总结:若函数返回类型为 void, 那么:
从语法上讲:函数是可以返回 undefined 的,至于显示返回,还是隐式返回,这无所谓
从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作,即使返回了 undefined 值
(5) object
关于 object 与 Object,实际开发中用的相对较少,因为范围太大了。
1. object
所有非原始类型,可存储:对象、函数、数组等,由于限制的范围别叫宽泛,在实际开发中使用的相对较少。
TypeScript
let a: object // a 能存储的类型是【非原始类型】
let a: Object // B 能存储的类型是可以调用到 Object 方法的类型
// // 以下代码,是将【非原始类型】赋给a,无警告
a = {}
a = {neme:'tom'}
a = [1,3,5,7,9]
a = fuction(){}
a = new String('123')
class Person {}
a = new Person{}
// 以下代码,是将【原始类型】赋给a,有警告
a = 1 // 警告:不能将类型 'number' 分配给类型 'object'
a = true // 警告:不能将类型 'boolean' 分配给类型 'object'
a = 'Hello' // 警告:不能将类型 'string' 分配给类型 'object'
a = null // 警告:不能将类型 'null' 分配给类型 'object'
a = nudefined // 警告:不能将类型 'undefined' 分配给类型 'object'
2. Object
官方描述:所有可以调用的 Object 方法的类型
简单记忆:除了 undefined 和 null 的任何值
由于限制的范围实在太大了,所以实际开发中使用率极低
TypeScript
b = {}
b = {neme:'tom'}
b = [1,3,5,7,9]
b = fuction(){}
b = new String('123')
class Person {}
b = new Person{}
b = 1
b = true
b = 'Hello'
b = null // 警告:不能将类型 'null' 分配给类型 'object'
b = undefined // 警告:不能将类型 'undefined' 分配给类型 'object'
3. 声明对象类型
- 实际开发中, 限制一般对象,通常使用以下形式
TypeScript
// 限制 person1 对象必须有 name 属性,age 为可选属性
let person1:{name: string, age?: number}
// 含义同上, 也能用分号做分割
let person2: {name: string; age?: number}
// 含义同上, 也能用分号做分割
let person3: {
name: string
age?: number
}
// 以下赋值均可以
person1 = {name:'李四', age:18}
person2 = {name:'张三'}
person3 = {name:'王五'}
// 以下赋值不合法,因为 person3 的类型限制中,没有对 gender 属性的说明
person3 = {name:'王五', gender:'男'}
2.索引签名:允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性。(具有动态属性的对象)
TypeScript
// 限制 person 对象必须有 name 属性, 可选 age 属性但值必须是数字,同时可以有任意数量、任意类
let person{
name: string
age?: number
[key: sting]: any //索引签名,完全可以不用 key 这个单词,换成其他可也可以
}
// 赋值合法
person = {
name:'张三'
按个: 18
gender: '男'
}
4. 声明函数类型
TypeScript
let count:(a: number, b: number) => number
count = function(x,y){
return x + y
}
备注:
typeScript 中的 => 在函数类型声明时表示函数类型,描述其参数类型和返回类型。
JavaScript 中的 => 是一种定义函数的语法,是具体的函数实现
函数类型声明还可以使用:接口、自定义类型等方式,下文中会详细讲解
5. 声明数组类型
TypeScript
let arr1: string[]
let arr2: Array<string>
arr1 = ['a','b','c']
arr2 = ['Hello','World']
// 上述代码中的 Array<string> 属于泛型,下文会讲解
(6) tuple
元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组用于精确描述一组值得类型,?表示可选元素
TypeScript
// 第一个元素必须是 string 类型,第二个元素必须是 number 类型。
let arr1: [strig, number]
// 第一个元素必须是 number类型,第二个元素是可选的,如果存在,必须是 boolean 类型。
let arr1: [number, boolean?]
// 第一个元素必须是 number类型,后面得元素可以是任意数量的 string 类型
let arr1: [strig, ...string[]]
// 可以赋值
arr1 = ['Hello', 123]
arr2 = [100, false]
arr2 = [200]
arr3 = [100, 'Hello', 'World']
arr3 = [100]
// 不可以赋值, arr1 声明时是两个元素,赋值的是三个
arr1 = ['Hello', 123, false]
(7) enum
枚举(enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护
以下代码的功能是:根据调用 walk 时传入的不同参数,执行不同的逻辑,存在的问题时调用 walk 时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的 up、down、left、right 是连续且相关的一组值,那此时九特别适合使用枚举(enum)
TypeScript
function walk(str:string){
if (str === 'up'){
console.log('向上走')
} else if (str === 'down'){
console.log('向下走')
} else if (str === 'left'){
console.log('向左走')
} else if (str === 'right'){
console.log('向右走')
} else {
console.log('未知方向')
}
}
walk('up')
walk('down')
walk('left')
walk('right')
1.数字枚举
常见的枚举类型,成员的值会自动递增 ,且数字枚举还具备反向映射的特点。
TypeScript
// 定义一个描述【上下左右】方向的枚举 Direction
enum Directio{
up,
Down,
Left,
Right
}
console.log(Direction) // 打印后会显示以下内容
/*
0:'Up',
1:'Up',
2:'Left',
3:'Right',
Up:0,
Down:'1,
Left:'2,
Right:3
*/
// 反向映射
console.log(Direction.Up)
console.log(Dirction.[0])
// 此行代码报错,枚举中的属性是只读的
Direction.Up = 'shang'
也可以指定枚举成员的初始值,其后的成员值会自动递增
TypeScript
enum Direction{
up = 6,
Down,
Left,
Right
}
console.log(Direction.Up) // 输出:6
console.log(Direction.Down) // 输出:7
使用数字枚举完成刚才的 walk 函数中的逻辑,代码更加直观易读,而且类型安全,也易维护
TypeScript
enum Directio{
up,
Down,
Left,
Right
}
function walk(n: Direction){
if (n === Direction.Up){
console.log('向上走')
} else if ((n === Direction.Down){
console.log('向下走')
} else if ((n === Direction.Left){
console.log('向左走')
} else if ((n === Direction.right){
console.log('向右走')
} else {
console.log('未知方向')
}
}
walk.(Direction.Up)
walk.(Direction.Down)
walk.(Direction.Left)
walk.(Direction.Right)
2. 字符串枚举
TypeScript
enum Directio{
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
let dir:Direction = Direaction.Up;
console.log(dir); // 输出:'Up'
3. 常量枚举
官方描述:是一种特殊枚举类型,它使用 const 关键字定义,在编译时会被内联 ,避免 生成一些额外的代码
所谓"内联"就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象,这可以减少生成的 JavaScript 代码量,并提高运行时性能
使用普通枚举的 Typescript 代码如下:
TypeScript
enum Directio{
up,
Down,
Left,
Right
}
console.log(Direction.Up)
// 编译后的 JavaScript 代码量较大
使用常量枚举的 TypeScript 代码如下:
TypeScript
const enum Directio{
up,
Down,
Left,
Right
}
let x = Direction.Up
// 编译后的 Javascript 代码量较小
"use strict"
let x = 0 /* Direction.Up */
(8) type
type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行类型复用和扩展
1.基本用法
类型别名使用 type 关键字定义,type 后跟类型名称,例如下面代码中 num 是类型别名
TypeScripttype num = number let price: num price = 100
- 联合类型
是一种高级类型,它表示一个值可以是几种不同类型之一。
TypeScripttype Status = number | string type Gender = '男' | '女' function printStatus(status: Status){ console.log(status) } function logGender(str: Gender){ console.log(str) } printStatus(404) printStatus(200) printStatus(501) logGender('男') logGender('女')
- 交叉类型
交叉类型(Itersection Types)允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。
TypeScript// 面积 type = Area = { herght: number width: number } // 地址 type = Address = { num: number cell: number room: string } type House = Area & Address const house: House = { height: 180 weight: 75 num: 6 cell: 3 room: '316' }
(9) 一个特殊情况
代码1(正常)
再函数定义时,限制函数返回值为 void,那么函数的返回值就必须是空。
TypeScriptfunction demo():void{ // 返回 undefined 合法 return undefined // 以下返回均不合法 return 100 return false return null return [] } demo()
代码2(特殊)
使用类型声明限制函数返回值 void 时, TypeScript 并不会严格要求函数返回空。
TypeScripttype LogFunc = () => void const f1: LogFunc = () => { return 100 // 允许返回非空值 } const f2: LogFunc = () => 200 // 允许返回非空值 cosnt f3: LogFunc = function () { return 300 // 允许返回非空值 }
为什么会这样?
是为了确保如下代码成立,我们知道 Array.prototype.push 的返回一个数字,而 Array。prototype.forEach 的方法期望其回调的返回类型是 void
TypeScriptconst src = [1, 2, 3] const dst = [0] src.forEach((el) => dst.push(el))
(10) 类相关知识
创建类
TypeScript
class Person{
// 属性声明
name: string
age: number
// 构造器
constructor(name: string, age:number) {
this.name = name
this.age = age
}
// 方法
speak(){
console.log((`姓名:${this.name}, 年龄:${this.age}`))
}
}
// Person 实例
const p1 = new Person('小明', 12)
p1()
类继承
TypeScript
class Student extends Person{
grade: string
// 构造器
constructor(name: sting, age: number, grade: string){
super(name, age)
this.grade = grade
}
// 备注本例中 Student 类不需要额外的属性, Student 的构造器可以忽略
// 重写从父类继承的方法
override speak(){
console.log((`姓名:${this.name}, 年龄: ${this.age}, 年级: $(this.grade)`))
}
// 子类自己的方法
study(){
console.log(`$(this.name)正在努力学习中...`)
}
}
(11) 属性修饰符
| 修饰符 | 含义 | 具体规则 |
|---|---|---|
| public | 公开的 | 可以被:类内部、子类、类外部访问 |
| protected | 受保护的 | 可以被:类内部子类访问 |
| private | 私有的 | 可以被:类内部访问 |
| readonly | 只读属性 | 属性无法修改 |
1. public 修饰符
TypeScriptclass Person{ // name 写了 public 修饰符, age 没写修饰符, 最终都是 public 修饰符 public name: string age: number // 构造器 constructor(name: string, age:number) { this.name = name this.age = age } // 方法 speak(){ // 类的【内部】可以访问 public 修饰符的 name 和 age console.log((`姓名:${this.name}, 年龄:${this.age}`)) } } // Person 实例 const p1 = new Person('小明', 12) // 类的【外部】可以访问 public 修饰的属性 console.log(p1.name) class Student extends Person{ // 构造器 constructor(name: sting, age: number, grade: string){ super(name, age) } study(){ // 【子类中】可以访问父类中 public 修饰的: name属性、age属性 console.log(`$(this.name)正在努力学习中...`) } }
2. 属性简写形式
TypeScript// 简写前 class Person{ // name 写了 public 修饰符, age 没写修饰符, 最终都是 public 修饰符 public name: string age: number // 构造器 constructor(name: string, age:number) { this.name = name this.age = age }
TypeScript// 简写后 class Person { constructor( public name:string, public age: number ) {} }
3. protected 修饰符
TypeScriptclass Person{ // name 和 age 是受保护属性,不能再类外部访问,但可以在【类】与【子类】中访问 constructor( protected name: string protected age: number ) // getDetails 是受保护方法,不能在类外部访问,但可以在【类】与【子类】中访问 protected getDetails(): string{ // 类中能访问受保护的 name 和 age 属性 return `name: ${this.name}, 年龄: ${this.age}` } // introduce 是公开方法,类、子类、类外部都能使用 introduce(){ // 类中能访问受保护的 getDetails 方法 console.log(this.getDetails()) } } const p1 = new Person('张三', 18) // 可以在类外部访问 introduce p1.introduce() p1.name // 报错,类的外部不能访问 p1.age // 报错,类的外部不能访问 p1.getDetails // 报错,类的外部不能访问
private 修饰符
TypeScriptclass Person{ constructor( public name: string public age:number private IDCard: string ) { } } private getPrivateInfo(){ return `身份证号码: $(this.IDcard)` } getInfo(){ retun `姓名: ${this.name}, 年龄: $(this.age)` } getFullInfo(){ return this.getInfo() + ', '+ this.getPrivateInfo() } const p1 = new Person('tom', 18, '210540198606271234') p1.name // 可访问 p1.age // 可访问 p1.IDCard // 不可访问 console.log(p1.getInfo()) // 可打印 console.log(p1.getFullInfo()) // 可打印 p1.getPrivateInfo() // 不能调用
readonly 修饰符
TypeScriptclass Car{ constructor( public readonly vin: string // 车辆识别码,只读属性 public readonly year: number // 出厂年份, 只读属性 public color: string public sound: string ) { } } // 打印车辆信息 displayInfo() { console.log(` 识别码: ${this.vin} 出厂年份: ${this.year} 颜色: ${this.color} 音响: ${this.sound} `) } const car = new Car('2HRSLJ2354592445', 2020, '黑色', 'Bose') car.displayInfo() // 以下代码均为错误: 不能修改 readonly 属性 car.vin = '3tHGFJOGMN873y423423' car.year = 2022
(12) 抽象类
- 概述:抽象类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构, 要求其派生类必须实现其中的抽象方法。
- 简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法
通过以下场景,理解抽象类:
定义一个抽象类 Package,表示所有包裹的基本结构,任何包裹都有重量属性 weight,包裹都需要计算运费。但不同类型的包裹(如:标准速度、特快转递)都有不同的运费计算方式,因此用于计算运费 calculate 方法是一个抽像方法,必须由具体的子类来实现
Package 类
TypeScript
abstract class Package{
// 构造方法
constructor(public weight:number){}
//抽象方法
constract calculate(): number
//具体方法
printPackage(){
console.log(`包裹重量为:${this.weight}kg, 运费为:${this.calculate()}元`)
}
}
class StandardPackage extends Package{
constructor(
weight: number
public unitPrice: number
){ super(weight)}
calculate(): number{
return this.weiht * this.unitPrice
}
}
class ExpressPackage extends Package{
constructor(
weight: number
public unitPrice: number
public additional: number
) {supre(weight)}
calculate(): number{
if(this.weight > 10){
return 10 * this.unitPrice + (this.weight - 10) * this.additional
} else {
return this.weight * this.unitPrice
}
}
}
}
StandardPackage 类继承了Package,实现了 calculate 方法:
expressPackage 类继承了Package,实现了calculateAmount 方法:
总结:何时使用抽象类
定义通用接口: 为一组相关的类定义通用的行为(方法或属性)时
提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现
确保关键实现: 强制派生类实现一些关键行为。
4.共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。
(13) interface (接口)
interface 是一种定义结构 的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定义格式,不能包含任何实现!
1. 定义类结构
TypeScript// PersonInterface 接口, 用于限制 Person 类的格式 interface PersonInterface{ name: string age: number speak(n: number): void } // 定义一个类 Person, 实现 PersonInterface 接口 class Person implements PersonInterface{ constructor( public name: string public age: number ){ } // 实现接口中的 speak 方法, 注意:实现 speek 时参数个数可以少于接口中的规定, 但不能多 speak(n: number): void{ for(let i=0; i<n; i++){ // 打印出包含名字和年龄的问候语句 console.log(`姓名:$(this.name), 年龄:${this.age}`) } } } // 创建一个 Person 类的实例 p1, 传入名字 'tom' 和年龄 18 const p1 = new Person('tom', 18) p1.speak(3) // 打印结果如下 // 姓名:tom, 18 // 姓名:tom, 18 // 姓名:tom, 18
2. 定义对象结构
TypeScriptinterface UserInterface{ name: sting readonly gender: sting // 只读属性 age?: number // 可选属性 run: (n: number) => void } cosnt user: UserInterface = { name: '张三' gender: '男' age:18 run(n) { console.log(`奔跑了${n}米`) } }
3. 定义函数结构
TypeScriptinterface CountInterface{'' (a: number, b: number): number } const count: CountInterface = (x, y) =>{ return x + y }
4. 接口之间的继承
TypeScriptinterface PersonInterface { name: string age: number } interface StudentInterface extends PersonInterface {' }
5. 接口自动合并(可重复定义)
TypeScriptinterface PersonInterface { name: string age: number } interface PersonInterface { gender: stinng } const p: PersonInterface = { name:'tom' age = 18 gender: '男' }
总结:何时使用接口?
定义对象的格式:描述数据模型、API 响应格式、配置对象......等等,是开发中最常用的
类的契约:规定一个类需要实现哪些属性和方法。
自动合并:一般用于扩展第三方库的类型,这种特性在大型项目中可能会用到。
(14) 一些相似概念的区别
1. interface 与 type 的区别
相同点:interface 和 type 都可以用于定义对象结构,两者在许多场景中时可以互换的
不同点:interface:更专注于定义对象和类的结构,支持继承、合并。
type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合
1. 使用 interface 定义 Person 对象
TypeScriptinterface PersonInterface { name: string age: number speak(): void } // 使用 type 定义 Person 对象 type PersonType = { name: string age: number speak(): void } let p1: PersonInterface = { name: 'tom' age: 18 speak() { console.log(this.name) } }
2. interface 可以集成、合并
TypeScriptinterface PerssonInterface{ name: string // 姓名 age: number // 年龄 } interface PersonInterface { speak(): void } interface StudentInterface extends PersonInterface { grade: string // 年级 } cosnt student: StudentInterface = { name: '李四' age: 18 grade: '高二' speak(){ console.log( this.name, this.age, this.grade) } }
3. type 的交叉类型
TypeScript// 使用 type 定义 Person 类型,并通过交叉类型实现属性的合并 type PersonType = { name: string age: number } & { speak: () => void } // 使用type 定义 Student 类型,并通过交叉类型继承 PersonType type StudentType = PersonType & { grade: string }
2. interface 与 抽象类的区别
相同点:都用于定义一个类的格式(应该遵循的契约)
不同点:interface:只能描述结构,不能由任何实现代码,一个类可以实现多个接口
抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能能继承一个抽象类
1.一个类可以实现多个接口
TypeScript// FlyInterface 接口 interface FlyInterface { fly(): void } // 定义 SwimInterface 接口 interface SwimInterface { swim(): void } // Duck 类实现了FlyInterface 和 SwimInterface 两个接口 class Duck implements FlyInterface, Swiminterface { fly(): void{ console.log('鸭子可以飞') } swim(): void{ console.log('鸭子可以游泳') } } // 创建一个 Duck 实例 const duck = new Duck() duck.fly() // 输出:鸭子可以飞 duck.swim() // 输出:鸭子可以游泳
五、泛型
泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。
举例:如下代码中<T>就是 泛型,(不一定非叫 T),设置泛型后即可在函数中使用T表示该类型
1.泛型函数
TypeScriptfunction logData<T>(data:T):T{ console.log(data) return data } logData<number>(100) logData<string>('hello')
2.泛型可以有多个
TypeScriptfunction logData<T, U>(data1:T, data2:U): T | U { console.log(data1, data2) return Data.now() % 2 ? data1: data2 } logData<number, string>(100, 'hello') logData<string, boolean>('ok', false)
3. 泛型接口
TypeScriptinterface PeronInterface<T>{ name: string age: number extraInfo: T } let p1: PersonInterface<string> let p2: PersonInterface<number> p1 = { name: '张三', age: 18, extraInfo: '好学生'} p2 = { name: '李四', age: 18, extraInfo: '坏学生'}
4. 泛型约束
TypeScriptinterface PersonInterface{ name: string age: number } function logPerson<T extends PersonInterface>(info: T): void{ console.log(`姓名:${info.name, 年龄:${info.age}`) } logPerson({name: '张三', age: 18})
5. 泛型类
TypeScriptclass Person <T> { constructor( public name: string public age: number public extraInfo: T ) {} speak() { console.log(`姓名:${this.name}, 年龄:${this.age}`) console.log(this.extraInfo) } } const p1 = new Person <string>('小明', 18, '好学生') type CarInfo = { brand: string color: string } const p2 = new Person<CarInfo>('小李', 25, { brand: '宝马', color: '白色'})
六、类型声明文件
类型声明文件是 TypeScript 中的一种特殊文件,通常以.d.ts 作为扩展名。它的主要作用是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示
demo.js
TypeScriptexport function add(a, b){ return a + b } export function mul(a, b){ return a * b }
demo.d.ts
TypeScriptdecleare function add(a: number, b: number): number decleare function mul(a: number, b: number): number export{add, mul}
index.ts
TypeScript// example.ts import {add, mul} from './demo.js' const x = add(2, 3) // x 类型为 number const y = mul(4, 5) // y 类型为 number console.log(x, y)
七、装饰器
1. 简介
- 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
- 装饰器自 2015 年在ECMAScript-6 中被提出到现在,以将近10年
- 截至目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持。
- 装饰器有 5 种:类、属性、方法、访问器、参数
备注:虽然TypeScript5.0中可以直接使用类装饰器,但为了确保其他装饰器可用,现阶段使用时,仍建议使用 experimentalDecorators 配置来开启装饰器支持,而且不排除在来的版本中,官方会进一步调整装饰器的相关语法
2. 类装饰器
(1) 基本语法
类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。
应用场景:日志记录、元数据添加、类功能增强
TypeScript
// 定义装饰器
function ClassDecorator(constructor: Function) {
console.log('类装饰器执行');
}
// 应用装饰器
@ClassDecorator
class MyClass {
constructor() {
console.log('MyClass实例化');
}
}
const obj = new MyClass();
// 输出:
// 类装饰器执行
// MyClass实例化
TypeScript
function LogClass(target: Function) {
console.log(`类 ${target.name} 被定义`);
}
@LogClass
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 输出:类 User 被定义
3. 装饰器工厂
应用场景:需要传参的装饰器
TypeScript
// 装饰器工厂:返回装饰器函数的函数
function Color(color: string) {
return function(target: Function) {
console.log(`这个类的颜色是: ${color}`);
}
}
@Color("红色")
class Car {
brand: string = "Toyota";
}
// 输出:这个类的颜色是: 红色
4. 装饰器组合
TypeScript
function First() {
console.log("第一个装饰器");
return function(target: Function) {}
}
function Second() {
console.log("第二个装饰器");
return function(target: Function) {}
}
// 从下往上执行
@First()
@Second()
class Example {}
// 输出:
// 第二个装饰器
// 第一个装饰器
5. 属性装饰器
应用场景:属性验证、只读属性、元数据标记
TypeScript
function ReadOnly(target: any, propertyName: string) {
console.log(`属性 ${propertyName} 被装饰`);
// 使属性只读
Object.defineProperty(target, propertyName, {
writable: false
});
}
class Product {
@ReadOnly
id: number = 1;
name: string = "手机";
}
const p = new Product();
p.id = 2; // 错误:Cannot assign to 'id' because it is read-only
6. 方法装饰器
应用场景:日志记录、性能监控、方法拦截
TypeScript
function LogMethod(target: any, methodName: string, descriptor: PropertyDescriptor) {
console.log(`方法 ${methodName} 被调用`);
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`调用参数: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`返回结果: ${result}`);
return result;
};
}
class Calculator {
@LogMethod
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// 输出:
// 调用参数: 2,3
// 返回结果: 5
7. 访问器装饰器
应用场景:控制getter/setter的行为
TypeScript
function Configurable(value: boolean) {
return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Person {
private _age: number = 20;
@Configurable(false) // 设置不可配置
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
}
8. 参数装饰器
应用场景:参数验证、依赖注入标记
TypeScript
function LogParameter(target: any, methodName: string, paramIndex: number) {
console.log(`参数装饰器: 方法 ${methodName} 的第 ${paramIndex} 个参数`);
}
class Greeter {
greet(@LogParameter name: string, @LogParameter age: number) {
return `Hello ${name}, age: ${age}`;
}
}
const g = new Greeter();
g.greet("张三", 25);
// 输出:
// 参数装饰器: 方法 greet 的第 0 个参数
// 参数装饰器: 方法 greet 的第 1 个参数
完整示例:简单的验证装饰器
TypeScript
// 方法装饰器:验证参数
function Validate() {
return function(target: any, methodName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
// 验证所有参数都不为空
for (const arg of args) {
if (!arg) {
throw new Error("参数不能为空");
}
}
return originalMethod.apply(this, args);
};
};
}
class UserService {
@Validate()
createUser(name: string, email: string) {
console.log(`创建用户: ${name}, ${email}`);
return { name, email };
}
}
const service = new UserService();
service.createUser("张三", "zhangsan@example.com"); // ✓ 成功
service.createUser("", "test@example.com"); // ✗ 报错:参数不能为空
启用装饰器
需要在 tsconfig.json 中配置:
TypeScript
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
总结
| 装饰器类型 | 参数 | 应用场景 |
|---|---|---|
| 类装饰器 | constructor: Function |
类增强、元数据 |
| 属性装饰器 | target, propertyName |
属性验证、只读 |
| 方法装饰器 | target, methodName, descriptor |
日志、性能监控 |
| 访问器装饰器 | target, propertyName, descriptor |
控制getter/setter |
| 参数装饰器 | target, methodName, paramIndex |
参数验证、依赖注入 |