TyperScript
一、TypeScript开发环境搭建
1、安装Node.js与npm
从Node.js官网下载并安装最新LTS版本,安装完成后通过终端验证
node -v # 检查Node.js版本
npm -v # 检查npm版本
说明:Node.js是TS的运行基础,npm用于包管理
2、安装TypeScript编译器
1、安装命令
npm install -g typescript #全局安装TyperScript编译器命令
tsc --version # 安装后验证,显示版本号即成功
2、TypeScript编译器的具体作用
-
代码转换:从 TypeScript 到 JavaScript
解析(Parsing)→
编译器通过扫描器(Scanner)将代码分解为语法单元(Tokens),并生成抽象语法树(AST)用来精确描述代码结构,去除注释和空 格,为后续阶段提供逻辑骨架。
即:Node.js和浏览器,只认识JS代码,不认识TS 代码。需要先将TS 代码转化为JS 代码,然后才能运行。类型检查(Type Checking)→
基于 AST,编译器通过符号表(Symbol Table)和绑定器(Binder)分析变量、函数和类的类型信息。若检测到类型不匹配(如将字符串 赋给数字变量)、未定义属性或参数类型错误,编译器会立即报错代码生成(Code Generation)→
通过发射器(Emitter)将 AST 转换为目标 JavaScript 代码,并根据 tsconfig.json 中的配置(如 target 选项)调整输出代码的 ECMAScript 版本。例如,装饰器(Decorators)会被转换为兼容的 ES5 代码
-
错误检测与诊断
编译器在多个阶段动态检测问题,显著减少运行时错误:
1、语法错误: 如缺少分号、括号不匹配,在解析阶段直接报错。
2、语义错误: 如类型不兼容、接口未实现,在类型检查阶段提示。
3、配置错误: 如tsconfig.json中无效选项,触发ConfigFileErrors。例如,代码 let a: number = “hello”; 会因类型不匹配触发 TypeErrors,避免潜在运行时崩溃
-
配置管理:tsconfig.json 的核心作用
通过 tsconfig.json,开发者可以精细控制编译行为:
关键配置示例:{ "compilerOptions": { "target": "ES6", // 输出 JS 版本 "strict": true, // 启用严格类型检查 "outDir": "./dist", // 输出目录 "skipLibCheck": true // 跳过第三方库类型检查(优化编译速度)[1](@ref) } }性能优化: 调整 skipLibCheck 可减少对第三方库类型定义的检查,显著缩短大型项目的编译时间
-
工具链集成与扩展
编译器支持与现代开发工具无缝协作:
1、实时开发: 结合 ts-node 可直接运行 .ts 文件,省去手动编译步骤;配合 nodemon 实现代码修改后自动重启
2、构建工具: 与 Webpack、Babel 等集成(如通过 ts-loader),支持模块打包、热更新和代码压缩
3、编辑器支持: 为 VS Code 等 IDE 提供类型推断和智能提示,提升编码效率 -
输出多样化与兼容性
编译器不仅生成 .js 文件,还可输出:
1、类型定义文件(.d.ts): 为其他 TypeScript 项目提供类型声明
2、源映射(.js.map): 便于调试阶段映射编译后的 JavaScript 代码到原始TypeScript 代码
3、跨平台兼容: 通过调整 target 选项,确保代码兼容旧版浏览器或 Node.js 环境
3、初始化项目配置
- 在项目根目录执行:
tsc --init # 生成tsconfig.json配置文件 - 关键配置项示例:
建议:开启strict模式以提高代码质量{ "compilerOptions": { "target": "ES6", // 编译目标JS版本 "outDir": "./dist", // 输出目录 "strict": true, // 启用严格类型检查 "module": "CommonJS" // 模块系统类型 }, "include": ["src/**/*"], // 需编译的源文件 "exclude": ["node_modules"] }
二、开发工具与进阶优化
1、编辑器配置(推荐VS Code)
- 安装VS Code及插件
- TypeScript插件:提供语法高亮、智能代码补全、静态类型检查与错误提示等
TypeScript and JavaScript Language Features插件已默认集成,无需单独安装。 - TSLint(可选):代码规范检查
- TypeScript插件:提供语法高亮、智能代码补全、静态类型检查与错误提示等
2、实时编译与调试工具
ts-node:直接运行TS文件,无需手动编译:
使用示例:npm i -g ts-nodets-node src/index.ts # 直接执行TS文件nodemon:监听文件变化自动重启:
适用场景:开发阶段快速调试npm install -g nodemon nodemon --exec ts-node src/index.ts
3、项目集成(Webpack示例)
安装依赖
npm install webpack webpack-cli ts-loader html-webpack-plugin -D
配置webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.ts',
module: { rules: [{ test: /\.ts$/, use: 'ts-loader' }] },
plugins: [new HtmlWebpackPlugin({ template: './index.html' })],
output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }
};
添加脚本命令(package.json)
"scripts": {
"build": "webpack",
"serve": "webpack serve"
}
作用:实现模块打包、热更新
三、编写,编译,并运行TS代码
1、编写TS代码:
在vscode中创建一个hello.ts的文件:内容如下
console.log("你好TS");
const age: number = 18;
console.log(age);
2、编译运行TS代码
第一种: 先将 TS 手动编译为 JS 文件再运行
在vscode终端中执行如下代码(会在同级目录下生成一个同名JS文件):
#将hello.ts这个TS文件编译成一个JS文件(编译成功后会在这个TS同级目录中生成一个同名的hello.js文件)
tsc hello.ts
#执行js文件
node hello.js //会在页面输出:你好TS 和 18
第二种:直接运行 TS 文件(无需手动编译)
在vscode终端中执行如下代码(不会在同级目录下生成一个同名JS文件):
因为第一种每次修改代码后,都要重复执行两个命令,才能运行TS代码,太繁琐
简化方式: 使用ts-node包,通过 ts-node 直接解析并执行 TS 文件,无需生成中间 JS 文件,适合快速调试。
#安装ts-node包
npm install -g ts-node typescript # 全局安装
#或者
npm install --save-dev ts-node typescript # 项目内安装
后直接在 Node.js 中执行TS 代码。
#适用ts-node命令执行 hello.ts这个文件
ts-node hello.ts //解释:ts-node 命令在内部偷偷的将TS->JS,然后,再运行JS代码。会在页面输出:你好TS 和18 (不会生成一个同名的JS文件了)
两种方式对比总结
| 方式 | 适用场景 | 优势 | 缺点 |
|---|---|---|---|
| tsc + node | 生产环境或复杂项目 | 生成标准 JS 文件,兼容性强 | 需额外编译步骤 |
| ts-node | 快速开发调试 | 无需手动编译,实时生效 | 不适合生产部署 |
建议: 开发阶段用 ts-node 快速迭代,发布前用 tsc 编译确保兼容性。结合自动编译(tsc -w)或热重载工具如 nodemon可进一步提升效率
四、TS申明变量关键字
在 TypeScript(以及 JavaScript)中 var 、const、let 是三种变量声明方式,它们的作用域、可变性和行为特性有显著差异。以下是它们的核心区别及使用建议:
val
-
用
val关键字申明的变量在 整个函数内有效,即使声明在代码块(如 if、for)内部,也能在函数内任意位置访问function example() { if (true) { var x = 10; } console.log(x); // 输出 10(块外仍可访问) } -
用
val关键字声明的变量存在变量提升
变量提升(Hoisting)是 JavaScript 的编译阶段行为,表现为 变量或函数的声明会被提升到作用域顶部,但赋值操作保留在原位。未声明前访问会返回undefinedconsole.log(a); // 输出 undefined(而非报错) var a = 1;上面代码实际等效于:
var a; // 声明被提升,初始化为 undefined console.log(a); // undefined a = 10; // 赋值仍保留在原位函数作用域:无论 var 声明在函数内何处,均被视为在函数顶部声明
-
用
val关键字声明的变量存在重复声明与可变性
用val关键字声明的变量允许重复声明。同一作用域内可多次声明同名变量,可能导致意外覆盖var c = 1; var c = 2; // 合法,c 被覆盖为 2
let和const
-
let和const存在暂时性死区,声明前访问会抛出ReferenceError,强制“先声明后使用”的规范,所以不会存在变量提升的问题
示例:console.log(a); // ❌ 报错:找不到名称“a” a 未定义 let a = 2; -
let和const禁止重复声明,同一作用域内重复声明会触发编译错误
示例:let a = 5; let a = 6; ❌ 报错:无法重新声明块范围变量“a”。 const b = 5; const b = 6; ❌ 报错:无法重新声明块范围变量“b”。 -
const的不可变性。
声明时必须初始化,且 不可重新赋值(但对象/数组的内容可修改)
示例:const PI = 3.14; PI = 3.1415; // ❌ 报错:常量不可变 const obj = { x: 1 }; obj.x = 2; // ✅ 合法(修改属性值) interface IPerson { id: number name: string age: number sex: string } // 在 TypeScript 中,const 关键字用于定义一个常量引用,这意味着 person1 变量本身是不可重新赋值的。 // 然而,对象本身的属性仍然可以被修改,因为 const 只限制了变量本身的引用,而不是对象的内容。 // 使用 const 可以向其他开发者明确传达:这个变量本身不应该被重新赋值,从而避免无意中修改引用。 const person1: IPerson = { id: 1, name: 'tom', age: 20, sex: '男' } // 尽管 person1 是用 const 定义的,但它的属性是可变的。例如,可以修改 person1 的属性值 person1.name = "lily" // ✅ 正确.可以修改属性的值 //person1 使用 const 定义的原因是为了确保该变量本身不会被重新赋值,如果想修改只能修改person1对象的属性值 //person1 = { id: 2, name: 'jerry', age: 22, sex: '男' }; // ❌ 报错:无法重新赋值给常量声明方式 使用场景 使用场景 val仅在需要函数级作用域或兼容旧代码时使用(现代开发中不推荐) 避免使用(除非处理遗留代码或需要函数级作用域提升) let需要重新赋值的变量(如循环计数器、状态变量) 必要时用(明确需要重新赋值的变量。) const默认选择,声明常量或引用类型(如对象、数组) 优先使用(减少意外修改,增强代码可维护性)
五、TypeScript常用数据类型
可以将 TypeScript中的常用基础类型分为两类:JavaScript已有类型 和 TypeScript 新增类型
1、JavaScript已有类型
JavaScript 的类型系统基于动态类型,主要分为原始类型和对象类型两大类:
原始类型
- 类型列表:
number、string、boolean、null、undefined、symbol(ES6 新增) - 特点: 类型名称与 JavaScript 原生类型完全一致,变量类型在运行时动态确定,允许随时改变类型
//数值类型 let count = 20; //当一个变量申明并且直接赋值后,可以不指明它的类型,因为TS可以自己做类型推断,推断出这个count是number类型 let height: number;//当声明一个变量后,没有直接赋值,建议指明它的类型 let age: number = 10; let weight: number = 75.6; //字符串类型 let myname: string = "张三"; //布尔类型 let isOk: boolean = false; //null类型 let snull: null = null; //undefined类型声明 let undefin: undefined = undefined; //可以把undefined赋值给void类型,但是没有任何意义 let unusable: void = undefined;
对象类型:对象(Object)
- object 类型
object(小写)表示非原始类型的对象(包括普通对象、数组、函数等),不接受原始值(如number、string、boolean、symbol、null、undefined)
在严格模式下,null 和 undefined 类型也不能赋给 object。let per: object = { name: "Alice", age: 30 } // ✅正确 let obj: object = { a: 1 }; // ✅正确 let arr: object = [1, 2, 3]; // ✅正确 let func: object = () => {}; // ✅正确 let invalid: object = "string"; // ❌ 错误: 不能将类型“string”分配给类型“object”。 let snull: object = null; // ❌ 错误: 不能将类型“null”分配给类型“object”。 let undefin: object = undefined // ❌ 错误: 不能将类型“undefined”分配给类型“object”。 - Object 类型
Object(大写):包含所有 Object 类实例的属性和方法,可接受原始值(通过包装对象隐式转换)
在严格模式下,null 和 undefined 类型也不能赋给 Object。let obj1: Object = 42; // ✅正确(隐式转换为 Number 对象) let obj2: Object = "hello"; // ✅正确(隐式转换为 String 对象) let snull: Object = null; // ❌ 错误: 不能将类型“null”分配给类型“Object”。 let undefin: Object = undefined // ❌ 错误: 不能将类型“undefined”分配给类型“Object”。
对象类型:数组(Array)
- 单类型数组示例:
//数组:方括号写法 let emptyArr: number[] = []; // 显式类型声明 emptyArr.push(1); // 动态添加元素合法 //数组:方括号写法 let arr1: number[] = [1, 2, 3]; //数组:泛型写法 let arr2: Array<number> = [1, 2, 4]; let arr3: Array<string> = ["a", "b", "c"]; - 联合类型与复杂类型数组
1、多类型元素(联合类型):通过 (Type1|Type2)[] 允许混合类型
2、对象数组:结合接口定义复杂结构let mixed: (number | string)[] = [1, "two"]; // 圆括号不可省略interface User { name: string; age: number; } let users: User[] = [{ name: "Alice", age: 30 }]; - 固定结构数组:元组(Tuple):
适用于元素数量和类型固定的场景(如坐标):
若元素类型或数量不匹配会编译报错let point: [number, number] = [10, 20]; // 必须严格匹配类型和数量
可通过索引访问类型(如type X = typeof point[0]) - 多维数组
通过嵌套语法声明多维结构let matrix: number[][] = [[1, 2], [3, 4]]; // 二维数组 let cube: number[][][] = [[[1]], [[2]]]; // 三维数组
对象类型:函数(Function)
- 函数的定义
//命名函数:定义个名称为greet的函数,函数带一个类型为string,名称为name的参数。 :void 表示函数没有返回值 function greet(name: string): void { console.log(`Hello, ${name}`); } //泛型函数:增强类型灵活性,支持多种类型参数: 函数的参数value为T类型,函数的返回值为T类型 function identity<T>(value: T): T { return value; } let output = identity("Hello"); // 自动推断为 string 类型 let output2 = identity(45); //回调函数:作为参数传递,需定义类型约束。 //参数callback的类型为一个没有返回值的匿名函数:(result: string) => void function fetchData<T>(callback: (result: T) => void, data: T): void { callback(data) } // 调用示例 fetchData<string>((result) => console.log(result), "success"); //输出:"success" fetchData<number>((count) => console.log(count * 2), 100); //输出:200 //匿名函数:通过函数表达式定义,常用于赋值或参数传递: const sum = function (a: number, b: number): number { return a + b; } //箭头函数:简化语法,自动绑定 this,适用于回调或简洁操作 const square = (x: number): number => x * x; console.log(square(5)) //输出 25 //闭包与默认参数 const counter = (start = 0) => () => start++; const c = counter(10); console.log(c()); // 10 console.log(c()); // 11 //泛型函数重载:处理复杂类型逻辑时,可通过重载提升类型精确度:根据输入类型返回不同的处理结果 function parseInput<T extends string | number>(input: T): T extends string ? string : number { // 将数字分支改为返回数值类型(如四舍五入) return ( typeof input === "string" ? input.trim() : Number(input.toFixed(2)) // ✅ 转换为 number ) as T extends string ? string : number; // 需配合类型断言 } //上面parseInput函数可以优化成如下 function parseInput1(input: string | number): string | number { return typeof input === "string" ? input.trim() : input.toFixed(2); } function FunSum<T>(x: T, y: T) { if (typeof x === "number" && typeof y === "number") { console.log(x + y) } } const sum1: <T>(x: T, y: T) => void = (x, y) => { if (typeof x === "number" && typeof y === "number") { console.log(x + y) } }; // 数值求和(需泛型约束) // 泛型约束(Type Constraints):通过 extends 关键字限制泛型类型的范围,确保类型满足特定条件: // T extends number 表示限制T的类型为number类型 const sum2: <T extends number>(x: T, y: T) => void = (x, y) => { console.log(x + y); // 需约束 T 为 number 类型 }; const sum3: <T = number>(x: T, y: T) => T = (x, y) => { return x; }; console.log(sum3<string>("ab", "d")); const sum4 = <T>(x: T, y: T) => { console.log(x as any + y as any); // 绕过类型检查 } const sum5 = (x: number): number => x * x; console.log(sum5(4)) //通过 T extends Constraint 限制泛型类型的合法范围 interface HasLength { length: number; } //限制T为HasLength类型 function logSize<T extends HasLength>(obj: T): void { console.log(obj.length); // 安全访问约束属性 } //默认泛型类型:为泛型参数提供默认值,简化调用: //T = number表示T的默认类型为number,当然你也可以根据实际情况传递其他类型 function createArray<T = number>(length: number, value: T): T[] { return new Array(length).fill(value); } const numArr = createArray(3, 42); // 默认 T 为 number const strArr = createArray<string>(3, "x"); // 显式指定 T 为 string
引用类型:类(class)
在 TypeScript 中,class 并不是一种简单的数据类型,而是一种用于定义类的语法结构。不过,从类型的角度来看,一个类在 TypeScript 中确实可以被用作一种引用类型(reference type),即它的实例类型
在原生 JavaScript 中,class 是从 ECMAScript 2015(ES6)开始引入的语法,用于定义对象构造器的语法糖。本质上,JavaScript 的 class 仍然是基于原型链(prototype-based)的继承机制实现的。
TypeScript 在 JavaScript 的基础上,为 class 提供了静态类型检查的功能。通过 TypeScript,你可以为类的属性和方法指定类型,从而增强代码的可读性和安全性。
-
类的定义
class Person { readonly id: number; //readonly修饰符,表示id属性是只读的,实例对象不能修改这个属性 name = "狄仁杰"; //直接赋值了,TS可以自动类型推断出name是string类型,相当于:name:string="狄仁杰" age: number; //因为age没有赋初始值,所以建议给它指定类型,否则它的类型就是any类型 //TS中用constructor关键字作为类的构造函数,构造函数是没有返回值的,所以不能指定返回类型 //类可以有构造函数,也可以不用构造函数 constructor(name: string, age: number) { //构造方法内部可以设置只读属性的初始值。 //如果在顶层和构造方法中同时设置了初始值,那么最终会以构造方法中设置的值为准,但是在其他方法中修改只读属性都会报错。 this.id = 1; this.name = name; this.age = age; } //类中的方法 greet(spe: string) { console.log(`大家好,我叫${this.name},我今年${this.age}岁,我会${spe}`) } } //用法: const p = new Person("狄仁杰", 18); p.greet("破案"); //输出:大家好,我叫狄仁杰,我今年18岁,我会破案 -
类的继承(extends)
在TypeScript中,使用extends关键字来指定一个类继承自另一个类
子类通过 extends 继承父类属性和方法,并通过super调用父类构造函数
在子类构造函数中必须调用super(),否则会报错(除非子类没有构造函数)
在子类构造函数中执行super()会调用父类的构造函数,并可以根据父类构造函数需要的参数传递相应的参数。
子类可以定义与父类同名的方法,覆盖父类的实现(这叫方法重写)class Employee extends Person { department: string; constructor(name: string, age: number, department: string) { super(name, age); // 子类必须调用父类构造函数 this.department = department; }; //子类方法 work() { //super.greet("跳舞"); // 子类中调用父类的 greet()方法。通过 super.methodName() 调用父类方法(即使子类重写了该方法) console.log(`${this.name} works in ${this.department}`); }; // 重写父类方法 greet(spe: string) { console.log(`大家好,我叫${this.name},我今年${this.age}岁,我是${this.department}部门的,我会${spe}`) } } -
抽象类(abstract): 定义抽象类作为基类,要求子类实现抽象方法:
抽象类不能被实例化,只能作为基类被继承。
包含抽象方法(无实现,需子类实现)和具体方法抽象类本身不能被实例化,但这并不意味着抽象类不能有构造函数。实际上,抽象类可以有构造函数,尽管它们不能直接实例化。抽象类 的构造函数主要用于以下目的:
1、初始化抽象类的成员: 如果抽象类有实例属性或需要执行的初始化逻辑,可以通过构造函数来实现。
2、被子类调用: 当子类实例化时,子类的构造函数会调用抽象类的构造函数(通过 super()),以便正确初始化从抽象类继承的部分abstract class Animal { abstract makeSound(): void; // 抽象方法 move(): void {console.log("Hello")}; // 具体方法 // 抽象类的构造函数。这里我们根据需要定义了一个带参数的构造函数。 //一般情况抽象类不需要定义构造函数。TypeScript 会自动提供一个无参数的构造函数 //constructor(protected name: string) { //console.log(`Initializing Animal with name: ${name}`); //} } class Dog extends Animal { /* * 默认构造函数 * 当一个类没有显式定义构造函数时,TypeScript 会自动提供一个无参数的构造函数 * 构造函数是隐式的,相当于:constructor() { super(); // 调用父类 Animal 的构造函数 } */ makeSound() { //实现父类的抽象方法 makeSound() console.log("Woof!"); } } //调用 const dog = new Dog() dog.move(); //输出:Hello dog.makeSound(); //输出:Woof! -
多态(Polymorphism): 通过继承和方法重写实现多态:
function animalSound(animal: Animal) { animal.makeSound(); // 运行时根据实际类型调用子类的方法 } //调用 animalSound(new Dog()); // 输出: Woof! -
类继承接口(implements)
当类通过implements关键字继承普通接口时,需满足以下规则:
1、强制实现所有成员: 必须实现接口中定义的所有非可选属性和方法,否则会报错
2、单继承或多继承: 一个类可以同时实现多个接口(如 class A implements B, C)
3、静态与实例分离: 接口仅约束实例成员,不涉及类的静态属性或构造函数
示例:interface Animal { name: string; makeSound(): void; } class Dog implements Animal { name: string; constructor(name: string) { this.name = name; } makeSound() { console.log("Woof!"); } // 必须实现接口方法 }类继承多个接口
一个类可以同时实现多个接口,只需用逗号分隔接口名称。
示例:interface Flyable { fly(): void; } interface Swimmable { swim(): void; } class Duck implements Animal, Flyable, Swimmable { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } makeSound(): void { console.log(`${this.name} says: Quack!`); } fly(): void { console.log(`${this.name} is flying.`); } swim(): void { console.log(`${this.name} is swimming.`); } } const duck = new Duck("Daffy", 5); duck.makeSound(); // 输出: Daffy says: Quack! duck.fly(); // 输出: Daffy is flying. duck.swim(); // 输出: Daffy is swimming.类继承泛型接口(泛型类实现泛型接口)
泛型接口允许接口中包含类型参数,类在实现时需要处理泛型参数,常见场景如下:
1、保留泛型参数: 实现时类本身需声明泛型,并与接口参数对应,使类成为泛型类
2、类型约束: 通过 extends 限制泛型范围,例如 T extends Animal 确保泛型参数必须符合特定类型
3、灵活性增强: 泛型接口适用于需要处理多种数据类型的场景(如存储库、集合等)interface Repository<T> { add(item: T): void; getAll(): T[]; } class InMemoryRepository<T> implements Repository<T> { private items: T[] = []; add(item: T) { this.items.push(item); } getAll() { return this.items; } } // 使用泛型类 const repo = new InMemoryRepository<string>(); repo.add("TypeScript"); console.log(repo.getAll()); // 输出 ["TypeScript"]接口与抽象类的区别
虽然抽象类和接口都可以用来定义类的行为,但它们有以下区别:1、接口: 只能定义属性和方法的签名,不能包含具体的实现。一个类可以实现多个接口。
2、抽象类: 可以包含属性、方法的具体实现,以及构造函数。一个类只能继承一个抽象类。// 使用接口 interface Shape { draw(): void; // 接口中的方法没有具体实现,只有方法签名 } class Circle implements Shape { // 实现接口中定义的 draw 方法 draw(): void { console.log("Drawing a circle"); // 具体实现:绘制一个圆形 } } // 使用抽象类 abstract class AbstractShape { abstract draw(): void; // 抽象方法 draw,子类必须实现这个方法 logMessage(): void { // 普通方法 logMessage,提供通用的实现 console.log("This is a shape."); } } class Rectangle extends AbstractShape { // 实现抽象类中定义的 draw 方法 draw(): void { console.log("Drawing a rectangle"); // 具体实现:绘制一个矩形 } } -
静态成员(Static Members):
在Class中,我们还可以定义静态属性和方法,它们属于类本身而不是类的实例。我们可以通过使用static关键字来定义静态成员。
例如,下面是一个使用静态属性和方法的示例:class MathUtils { static PI: number = 3.14; //静态常量 PI static calculateArea(radius: number) { return MathUtils.PI * radius * radius; //使用类常量PI计算面积: πr² } } //调用 const ab= MathUtils.calculateArea(5); console.log(ab); //输出:78.5
2、TypeScript 新增类型
TypeScript 在 JavaScript 的基础上引入了静态类型系统,新增了以下类型和特性:
1、联合类型(Union Types)2、字面量类型(Literal Types)3、元组(Tuple)
4、枚举(Enum) 5、类型别名(Type Aliases) 6、void 7、never
8、 类型断言 7、泛型(Generics) 10、 类型守卫 11、接口(Interface)
联合类型(Union Types)
联合类型:用 | 表示变量可接受多种类型。例如:
let id: number | string = "ID123"; // 允许数字或字符串。
交叉类型(Intersection Types)
交叉类型是 TypeScript 中用于合并多个类型的核心特性,通过 & 操作符将多个类型的属性聚合成一个新类型。它类似于“取并集”的逻辑,常用于增强类型灵活性或复用复杂结构。
定义: type Combined = TypeA & TypeB;
表示 Combined 类型同时包含 TypeA 和 TypeB 的所有属性
关键点:
1、属性叠加: 新类型拥有所有输入类型的成员。
2、冲突处理: 若属性名重复但类型不兼容,会报错;兼容时合并为联合类型。
interface User {
id: number;
name: string;
email?: string;
}
interface Admin {
role: string;
permissions: string[];
}
// 交叉类型:同时拥有 User 和 Admin 的属性
type AdminUser = User & Admin;
const admin: AdminUser = {
id: 1,
name: "Alice",
role: "superadmin",
permissions: ["create", "delete"],
email:"123@qq.com" //因为email在User中是可选属性,admin对象中可以定义email也可以不定义email。
};
字面量类型(Literal Types)
在TypeScript中声明字面量类型的关键字是 type和const
字面量类型:直接限定变量值为特定值。例如:
//使用type关键字声明字面量类型
type Status = 'loading' | 'success' | 'error';
type Flag = true | false;
let isEnabled: Flag;
isEnabled = true; // ✅ 正确
isEnabled = 'yes'; // ❌ 错误: 类型 '"yes"' 不可分配给类型 'Flag'
//const关键字通常用于声明常量,但在TypeScript中,它也可以用于定义字面量类型
const str = "true"
type typeflag = typeof str//typeflag的值为"true"
let color: "red" | "blue"; //定义了一个名为 color 的变量,其类型是一个联合字面量类型"red" | "blue",变量color仅允许赋值 "red" 或 "blue"
let str1 = "Hello TS"; //str1的类型为:string
const str2 = "Hello TS"; //str2的类型为:"Hello TS"

let str1: 18 = 18; //当把str1的类型指定为数值类型的18后,那么它的值就只能为18了,不能为19或者其他,它相当于const str1 = 18;
let str2: "中国" = "中国"; //当把str2的类型指定为字符串类型的"中国"后,那么它的值就只能为"中国"不能为其他的值了,它相当于const str2 = "中国";
let str3: true = true; //当把str3的类型指定为布尔类型的true后,那么它的值就只能为true,不能为false ,它相当于const str32: true = true;
字面量类型的用处:
使用模式:字面量类型一般配合联合类型一起使用。
使用场景:一般用来表示一组明确的可选值列表。
比如:在贪吃蛇游戏中,游戏的方向的可选值只能是上,下,左,右中的任意一个值(它的用处和枚举很类似)
function changeDirection(direction: "上" | "下" | "左" | "右") {
console.log("您按了" + direction);
}
changeDirection("上") //输出:您按了 上
相比string类型,字面量类型更加精确,严谨 ,不给你传错值的机会,固定你可以传哪些值。
元组(Tuple)
元组(Tuple):固定长度和元素类型的数组。例如:
//元组:元组类型表示允许一个已知元素数量和类型的数组,各个元素的类型可以不相同,也可以相同。 元祖严格约束数据的长度和类型。
let point: [number, string] = [122.5, "北京"];
let arr: [number, number, string, boolean] = [1, 2, "abc", true];
枚举(Enum)
枚举(enum) 是一种特殊的类型,它允许你定义一组命名的常量。枚举成员在默认情况下会被分配一个数值,从 0 开始递增
//枚举:用enum关键字来声明枚举,枚举成员是有值的,默认为从0开始自增的数值,
// 我们把枚举成员的值为数字的枚举称为:数字枚举
enum Direction1 {
Up, //Up的默认为从0开始自增的数值,所以它的值默认为0 它相当于Up=0
Down, //它相当于Down=1
Left, //它相当于Left=2
Right, //它相当于Right=3
}
//用法:
console.log(Direction1.Up); //输出:0
enum Direction2 {
Up = 2, //Up的默认为从0开始自增的数值,但是此处我们已经指定了它的值为2
Down, //它相当于Down=3
Left, //它相当于Left=4
Right, //它相当于Right=5
}
enum Direction3 {
Up = 2, //Up的默认为从0开始自增的数值,但是此处我们已经指定了它的值为2
Down, // Down的值为3
Left = 8,
Right, //Right的值为9
}
enum Direction4 {
Up = 2, //我们也可以手动指定枚举的值
Down = 4,
Left = 6,
Right = 8,
}
//字符串枚举:枚举成员的值为字符串的时候此枚举为字符串枚举,
//字符串枚举没有自增长的行为,因此字符串枚举的每个成员必须要有初始值
enum Direction5 {
Up = "上",
Down = "下",
Left = "左",
Right = "右",
}
//用法:
console.log(Direction5.Up); //输出:上
//案例:用法
function changeDirection(direction: Direction5): void {
console.log(direction);
}
changeDirection(Direction5.Down) //输出:下

类型别名(Type Aliases)
类型别名(Type Aliases):简化复杂类型定义。例如:
type Point = { x: number; y: number }; // 复用坐标结构
//类型别名: 类型别名其实就是自己先定义好一个类型,后续有其他变量要使用这个类型的时候,直接用了,避免每次写重复的类型代码:
type CustomArry = (string | number | boolean)[];
let arr1: CustomArry = [1, 2, "a"];
let arr2: CustomArry = ["a", "b", "c", 1, 2, 3, false];
//类型别名的作用是当存在多个变量使用同一个类型的时候,避免每次都写重复很长的数据类型 如:(string | number | boolean)[]
// let arr1: (string | number | boolean)[] = [1, 2, "a"];
// let arr2: (string | number | boolean)[] = ["a", "b", "c", 1, 2, 3, false];
//用type关键字定义一个Person类型别名
type Person = {
name: string;
age: number;
[key: string]: any; // 任意属性:即这个key可以是任意值的字符串
};
//用法:定义一个变量user,变量的类型为Person
let user: Person = {
name: "Alice",
age: 25,
job: "Engineer",
};
void
void:表示函数无返回值。例如:
function log(message: string): void { console.log(message); }
never
never:表示永不返回的值(如抛出错误或死循环)。例如:
function error(): never { throw new Error(); }
never与 void 的区别
function noop(): void {} // 返回 undefined
function fail(): never { throw Error(); } // 无返回值
any
any:关闭类型检查,慎用(仅用于兼容旧代码或动态场景)
//any:任意类型,可以表示为任意类型,未标注类型时默认为此类型。
let temp: any;
temp = 123;
temp = false;
temp = "abc";
unknown
unknown:类型安全的 any,需类型收窄,或类型断言后使用。
any 和unknown 都代表 TypeScript 中的任何值。
但有一个关键的区别:any 绕过了编译器的类型检查,本质上关闭了 TypeScript 对该变量的好处。
另一方面,unknown 保持类型检查完整,确保在对变量执行操作之前断言或缩小变量的类型。
unknown 的两种操作方式
-
类型断言(Type Assertion)
开发者主动告知编译器具体类型,语法为 value as Type:let data: unknown = fetchExternalData(); // 外部数据来源 data.trim(); // 错误:Object is of type 'unknown'let input: unknown = "Hello"; // 必须先断言为 string,在使用str.toUpperCase() let str: string = input as string; console.log(str.toUpperCase()); // 安全风险: 若断言错误(如 input 实际是数字),运行时可能崩溃
-
类型收窄(Type Narrowing)
通过条件逻辑让编译器自动推断更精确的类型:let data: unknown = fetchData(); //unknown类型的使用就是必须要有这个类型收窄。收窄为string → 安全 if (typeof data === "string"){ console.log(data.toUpperCase() );示例2
/** * 处理未知类型的值,并在值为字符串时将其转为大写输出 * @param value - 任意类型的输入值(类型为 unknown) */ function handleValue(value: unknown) { // 使用类型守卫检查 value 是否为 string 类型 if (typeof value === "string") { //unknown的使用就是必须要有这个类型收窄。 // 类型收窄:此时 TypeScript 知道 value 是 string 类型 // 调用字符串的 toUpperCase() 方法(类型安全) console.log(value.toUpperCase()); } // 注意:如果 value 不是 string 类型,此处不会有任何操作 // 也不会抛出运行时错误(因为 unknown 类型要求显式类型检查) }
接口(Interface)
-
接口的定义
接口(Interface):接口通过interface关键字声明。例如:interface User { name: string; age: number; readonly id: string; // 只读属性 isAdmin?: boolean; // 可选属性。通过 ? 标记可选属性:允许对象不包含 isAdmin 字段 greet(): void; // 方法,当前方法没有返回值。 }接口的只读属性:使用
readonly关键字防止属性被修改,只读属性初始化后不可变interface Point { readonly x: number; readonly y: number; } const p: Point = { x: 10, y: 20 }; //只读属性只能在初始化时赋值,之后不能修改 p.x = 5; // ❌ 编译报错:无法分配到 "x",因为它是只读属性,初始化后无法重新赋值。readonly与 const 的区别
const 用于变量级别的不可重新赋值,如 const a = 1; a = 2; 会报错。
readonly 用于对象属性级别的不可修改,例如:const obj = { a: 1 } as const; // 对象内部属性也变为只读 obj.a = 2; // ❌ 报错 -
接口的继承
// 声明一个名称为IAnimal的接口 interface IAnimal { name: string; } // 声明一个名称IDog 的接口,它继承了IAnimal接口 (extends关键字用于接口的继承。表示IDog 继承IAnimal。 // 此时IDog中除了有自己定义的ability属性外,还多了一个从IAnimal接口中继承来的name属性) interface IDog extends IAnimal { ability: string; } // 约束对象类型 // 声明一个名称为pup的对象,并指明了它是IDog 类型(这里的接口IDog 就是用来给对象pup指定类型的) const pup: IDog = { name: "小狗", ability: "吃骨头" }; // 约束数组类型 const pups: IDog [] = [{ name: "大黄", ability: "看门" },{ name: "小黑", ability: "吃奶" }];接口多继承存在的问题:
案例1:interface Father { commonMethod(): string; } interface Mother { commonMethod(): number; } interface Son extends Father, Mother { } //❌ 报错:接口“Son”不能同时扩展类型“Father”和“Mother”。“Father”和“Mother”类型的命名属性“commonMethod”不完全相同这个错误是由于 TypeScript 的接口继承规则导致的。具体来说,当你让
Son接口同时继承Father和Mother时,TypeScript 会尝试合并这两个接口的定义。然而,Father和Mother都定义了commonMethod方法,但它们的返回类型不同(string和number),这会导致类型冲突。
1、接口继承冲突: 在 TypeScript 中,当一个接口继承多个父接口时,所有父接口中的同名方法必须具有相同的签名(包括返回类型)。在你的代码中,Father 的commonMethod返回string,而Mother的
2、类型不兼容:string和number是两种完全不同的类型,TypeScript 不允许一个方法同时返回这两种类型。案例2:
interface Father { commonMethod(): string; } interface Mother { commonMethod(): number; } interface Son extends Father, Mother { commonMethod(): string | number; // ❌ 报错:接口“Son”错误扩展接口“Father”。 在这些类型中,"commonMethod()" 返回的类型不兼容。不能将类型“string | number”分配给类型“string”。不能将类型“number”分配给类型“string”。 }这个错误是因为 TypeScript 的接口继承规则要求
子接口的方法签名必须与父接口的方法签名完全兼容。在你的代码中,Son接口继承了Father,但你试图在Son中将commonMethod的返回类型更改为string | number,这与Father的commonMethod返回类型string不兼容。
1、接口继承规则: 当一个接口(如Son)继承另一个接口(如Father)时,Son必须完全实现Father中定义的所有方法,包括方法的返回类型。
2、类型不兼容:string | number是一个联合类型,表示返回值可以是string或number。然而,Father的commonMethod明确要求返回string。因此,将Son的commonMethod定义为string | number会导致类型不兼容。解决方案1: 使用函数重载
通过 重载签名 明确不同场景的返回值:interface Father { commonMethod(): string; } interface Mother { commonMethod(): number; } interface Son extends Father, Mother { // 重载声明 commonMethod(): string; commonMethod(): number; }注意: 需在实现时通过条件判断返回不同类型,例如:
class SonImpl implements Son { commonMethod(): string | number { return Math.random() > 0.5 ? "success" : 42; } }解决方案2: 统一父接口的返回值类型
若业务允许,调整父接口的返回值类型为 联合类型:interface Father { commonMethod(): string | number; // 允许返回 string 或 number } interface Mother { commonMethod(): string | number; // 允许返回 string 或 number } interface Son extends Father, Mother { commonMethod(): string | number; // 兼容所有父接口 }优点: 通过联合类型确保所有父接口的类型兼容性
-
接口继承类
接口继承类的问题:
接口可通过extends继承类的类型结构,而不能继承类的实现,常用于扩展已有类的类型定义:
正确的做法是让类通过 implements 实现接口,或者将 Person 改为接口。// 定义一个 Person 类,表示一个人的基本信息 class Person { name: string; // 姓名属性,类型为 string age: number; // 年龄属性,类型为 number // 构造函数,用于初始化 Person 类的实例 constructor(name: string, age: number) { this.name = name, this.age = age; // 初始化 name 和 age 属性 } // say 方法,输出 "Hello" say(): void { console.log("Hello"); } } // 定义一个接口 IEmployee,继承自 Person 类 // 注意:在 TypeScript 中,接口不能直接继承类,但可以继承类的类型结构 // 实际使用中,建议用 `implements` 让类实现接口,而不是接口继承类 interface IEmployee extends Person { jobTitle: string; // 扩展的 jobTitle 属性,表示职位,类型为 string } // 创建一个符合 IEmployee 接口的对象 const worker: IEmployee = { name: "Alice", // 姓名,来自 Person age: 25, // 年龄,来自 Person jobTitle: "Engineer", // 职位,IEmployee 特有的属性 say() {} // 实现 say 方法(空实现,因为接口不强制方法逻辑) }; -
泛型接口
案例1// 定义一个泛型接口 IPair,用于表示包含两个不同类型元素的键值对 interface IPair<T, U> { first: T; second: U; } // 创建一个符合 IPair<string, number> 接口类型的对象 mixedPair,并赋值 // 其中 first 是字符串类型,second 是数字类型 const mixedPair: IPair<string, number> = { first: "score", second: 100 };案例2
//该接口描述了一个函数类型,接受两个类型为T的参数并返回一个数字。然后,使用这个接口声明了一个具体的比较函数numberCompare,用于比较两个数字 interface Comparator<T> { (a: T, b: T): number; // 定义比较函数类型 } const numberCompare: Comparator<number> = (a, b) => a - b; let num= numberCompare(10,8) //调用函数,num的值为2 const numberCompare: Comparator<number> = (a, b) => a + b; let num2= numberCompare(10,8) //调用函数,num2的值为18 //上面接口类型相当于: function numberCompare(a: number, b: number): number { return a - b; } -
接口的使用场景
接口声明后,直接使用接口名称作为对象的类型
当一个对象类型被多次使用时,一般使用接口(interface)来描述对象的类型,达到复用的目的接口只能给对象指定类型
接口声明后,直接使用接口名称作为对象的类型
-
接口与类型别名的区别
相同点:接口与类型别名都可以为对象指定类型不同点:接口只能给对象指定类型,但是类型别名可以给任意类型指定别名例如:
//类型别名可以为任意类型指定类型,而在这里接口则不行,接口只能为对象指定类型 type Animal = number | string; let kitty1: Animal = 14; let kitty2: Animal = "14";
六、类型断言 as
1、什么是类型断言
类型断言(Type Assertion)是 TypeScript 中开发者主动告知编译器某个值的具体类型的语法,用于覆盖编译器的默认类型推断。它不会改变运行时类型,仅在编译阶段生效,类似其他语言中的“类型转换”,但更强调开发者的主动声明
2、类型断言的语法
- 尖括号语法:
<Type>valuelet strLength = (<string>someValue).length; - as 语法
value as Type(推荐)let strLength = (someValue as string).length;
注意: 在 JSX 或 React 中,尖括号语法可能与标签冲突,因此统一建议使用 as 语法
断言类型需与实际类型存在兼容关系(子类型或父类型)。若需绕过限制,可通过双重断言(先断言为 unknown 或 any)实现,但需谨慎
const num = 1 as unknown as string; // 绕过类型检查(可能引发运行时错误)
3、核心使用案例
-
处理
any或unknown类型
当变量类型为any或unknown时,断言可明确具体类型以安全操作const data: unknown = "hello"; const length = (data as string).length; // 断言为 string 类型 -
联合类型的类型收窄
当联合类型需要访问某个分支的特有属性时,断言可指定具体类型interface Cat { run(): void; } interface Fish { swim(): void; } function isFish(animal: Cat | Fish) { if (typeof (animal as Fish).swim === "function") return true; // 断言为 Fish 类型 } -
操作 DOM 元素
获取 DOM 元素时,断言具体类型以访问属性或方法const input = document.getElementById("username") as HTMLInputElement; input.value = "new value"; // 直接操作输入框的 value -
绕过对象字面量严格检查
当对象字面量包含多余属性时,通过断言忽略严格检查/** * 释义:目标类型 { x: number } 要求obj对象必须包含 x 属性且类型为 number, * 而原始对象 { x: 0, y: 0 } 满足此要求(且 y 属性被视作多余属性) * TypeScript 允许忽略多余属性,只要目标类型的必填属性存在且类型匹配,因此断言合法 **/ const obj = { x: 0, y: 0 } as { x: number }; // 忽略多余的 y 属性 -
非空断言
!
非空断言是 TypeScript 中的一个特性,允许你告诉编译器某个值不会是 null 或 undefined。
非空断言使用!后缀操作符:明确知道变量不为空时,即使 TypeScript 认为可能返回 null,你明确告诉它不会
//示例1: // 即使 TypeScript 认为 getElementById 可能返回 null,你明确告诉它不会 const element = document.getElementById("id")!; //示例2:对象属性访问 interface User { name: string; age?: number; } const user: User = { name: "Alice" }; const age = user.age!.toString(); // 告诉编译器 age 存在 //示例3:函数调用后 function getUserName(): string | undefined { /* ... */ } const name = getUserName()!.toUpperCase(); // 告诉编译器返回值不为 undefined -
const 断言:创建不可变类型
const arr = [1, 2, 3] as const; // 类型为 readonly [1, 2, 3] -
双重断言(Double Assertion)
什么是双重断言:
在TypeScript中,双重断言是指对一个值进行两次类型断言的操作,通常用于满足特定的类型需求,但这种技巧可能带来风险,因为它会绕过类型检查,导致潜在的运行时错误。
双重断言通常通过两次使用as关键字实现,例如:const value: any = "Hello, TypeScript"; const strValue = (value as unknown) as string; // 双重断言双重断言的作用:
解决类型不兼容问题:当需要强制将一个类型转换为另一个不兼容的类型时,双重断言可以绕过编译器的类型检查。例如,将一个 Event 类型的对象断言为 HTMLElement 类型:function handler(event: Event) { const element = (event as any) as HTMLElement; // 双重断言 }在上述代码中,Event 和 HTMLElement 类型不兼容,直接断言会报错,因此使用双重断言绕过类型检查。
双重断言的风险:
绕过类型安全:双重断言会告诉编译器“相信我,我知道我在做什么”,但如果断言错误,可能会导致运行时错误。例如,将一个非函数值断言为函数并调用:const value: any = "not a function"; const func = (value as unknown) as () => void; // 双重断言 func(); // 运行时错误:value is not a function
七、类型守卫
1. 类型守卫的定义
类型守卫是 TypeScript 中用于在运行时缩小变量类型范围的一种技术。通过条件判断(如 typeof、instanceof、in 操作符或自定义类型谓词),TypeScript 编译器能够确定变量在特定代码块中的具体类型,从而避免不必要的类型错误或 any 的滥用。
2. 类型守卫的核心作用
解决联合类型的不确定性:当变量类型为联合类型(如 string | number)时,类型守卫可以明确当前分支下的实际类型。
提升代码安全性:确保在特定代码路径中只能访问目标类型的属性和方法。
替代 any 的粗暴类型转换:通过显式检查实现更安全的类型操作。
3. 类型守卫的常见实现方式
3.1、typeof 类型守卫
适用于原始类型(string、number、boolean、symbol、bigint、undefined、function)。
示例:
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // ✅ 类型收窄为 string,安全访问 length
} else {
console.log(value.toFixed(2)); // ✅ 类型收窄为 number,安全调用 toFixed
}
}
3.2、instanceof 类型守卫
适用于对象类型(如类实例或构造函数)。
instanceof 是 JavaScript 和 TypeScript 中的一个运算符,用于检查一个对象是否是某个构造函数(类)的实例,或属于其原型链上的某个类。它通过比较对象的原型链与构造函数的 prototype 属性来判断类型关系,是面向对象编程中实现类型检查和多态的重要工具。
示例:
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function makeSound(animal: Dog | Cat) {
//判断变量 animal 是否是类 Dog 的实例
if (animal instanceof Dog) {
animal.bark(); // ✅ 类型收窄为 Dog
} else {
animal.meow(); // ✅ 类型收窄为 Cat
}
}
-
原型链追溯
通过 proto 属性向上查找原型链,直到匹配到构造函数的 prototype。
类比:
对象 dog 的原型链:dog → Dog.prototype → Animal.prototype → Object.prototype。
instanceof 会检查构造函数的 prototype 是否存在于对象的原型链中。 -
与 typeof 的区别
运算符 检查目标 适用场景 typeof 原始类型(如 string、number) 基础类型检查 instanceof 对象类型(类、构造函数) 面向对象编程中的实例判断
3.3、in 操作符类型守卫
检查对象是否包含特定属性(适用于接口或类)。
示例:
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
function move(animal: Bird | Fish) {
//检查对象 animal 是否包含 fly 属性
if ("fly" in animal) {
animal.fly(); // ✅ 类型收窄为 Bird
} else {
animal.swim(); // ✅ 类型收窄为 Fish
}
}
3.4、自定义类型谓词(Type Predicate)
通过函数返回布尔值并声明类型谓词,实现更灵活的类型检查。
语法:function isType(param: any): param is TargetType
示例:
interface Admin {
role: "admin";
permissions: string[];
}
interface User {
role: "user";
preferences: Record<string, unknown>;
}
/**
* 自定义类型守卫函数:检查用户是否为管理员(Admin)
*
* @param user - 待检查的用户对象(基础类型为 User)
* @returns 返回一个类型谓词(type predicate):
* - true : 表示 user 是 Admin 类型
* - false : 表示 user 不是 Admin 类型
*
* @remarks
* - TypeScript 编译时并不知道user具体是User类型还是Admin类型!因为 user 被声明为 User | Admin
* - 此时,我们就要将这个user对象具体下来,user要么是User类型,要么是admin类型,这就是user的类型收窄
*
* - 1. 运行时检查
* - isAdmin(user) 的代码会在运行时检查 user.role === "admin",并返回布尔值。
* - 如果返回 true,说明此时 user 实际上 是 Admin 类型。
* - 2. 编译时类型映射
* - user is Admin 的作用是 将运行时的检查结果映射到编译时的类型系统:
* - 如果return user.role === "admin"的结果是返回 true,TypeScript 将 user 的类型收窄为 Admin。即:user的类型不再是原先的 User | Admin 而是现在的Admin
* - 如果return user.role === "admin"的结果是返回 false,收窄为 User。即:user的类型不再是原先的 User | Admin 而是现在的User
*
*
** /
function isAdmin(user: Admin | User): user is Admin {
return user.role === "admin"; // 返回 true 时,user 类型收窄为 Admin
}
function handleUser(user: Admin | User) {
// 如果isAdmin(user)===true,此时user的类型为Admin
if (isAdmin(user)) {
console.log(user.permissions); // ✅ 类型收窄为 Admin
} else {
console.log(user.preferences); // ✅ 类型收窄为 User
}
}
3.5、可辨识联合类型(Discriminated Unions)
通过公共属性(如 type 字段)区分不同类型,结合类型守卫实现安全访问。
示例:
function parseInput(input: unknown) {
if (typeof input === "string") {
return input.toUpperCase(); // ✅ 类型收窄为 string
} else if (Array.isArray(input)) {
return input.map(item => item.toString()); // ✅ 类型收窄为 any[](需进一步处理)
}
throw new Error("Unsupported input type");
}
4. 类型守卫与 unknown 类型的结合
unknown 是比 any 更安全的类型,但必须通过类型守卫才能访问其属性。
示例:
function parseInput(input: unknown) {
if (typeof input === "string") {
return input.toUpperCase(); // ✅ 类型收窄为 string
} else if (Array.isArray(input)) {
return input.map(item => item.toString()); // ✅ 类型收窄为 any[](需进一步处理)
}
throw new Error("Unsupported input type");
}
5.类型守卫的底层原理
控制流分析(Control Flow Analysis):TypeScript 编译器通过分析 if、switch 等语句的分支逻辑,自动缩小变量的类型范围。
类型谓词标记:自定义类型谓词函数通过 param is TargetType 显式告诉编译器类型收窄的结果。
6. 类型守卫的常见误区
忽略非目标类型的处理:未覆盖所有联合类型分支时,可能导致类型推断错误。
function logValue(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // ✅ 正确
}
// ❌ 错误:未处理 number 类型,可能导致后续逻辑问题
}
过度依赖 any 转换:类型守卫应优先于 as 断言,避免隐式类型风险。
// ❌ 不推荐:直接断言为 string 可能导致运行时错误
function unsafeHandle(value: unknown) {
const str = value as string; // 危险!
console.log(str.toUpperCase());
}
7. 类型守卫的最佳实践
优先使用内置类型守卫:优先选择 typeof、instanceof、in 等内置守卫。
封装复杂逻辑:对复杂类型检查封装为自定义类型谓词函数。
保持代码可读性:通过清晰的分支逻辑和注释说明类型收窄的原因。
结合 strict 模式:在 tsconfig.json 中启用 "strict": true,强制类型检查。
8.类型守卫总结
类型守卫是 TypeScript 类型系统中的核心工具,通过显式的类型检查和编译器的智能推断,解决了联合类型的不确定性问题,提升了代码的安全性和可维护性。无论是处理原始类型、对象类型,还是复杂场景下的类型区分,类型守卫都能提供清晰的解决方案。
关键点:
✅ 类型收窄:通过条件判断缩小变量类型范围。
✅ 安全访问:确保在特定分支中只访问目标类型的成员。
✅ 灵活扩展:支持自定义逻辑和可辨识联合类型等高级用法。
八、装饰器(Decorators)
描述
TypeScript 装饰器是一种元编程工具,允许开发者在不修改原始代码的情况下增强类、方法、属性或参数的行为
定义
装饰器是一种特殊函数,通过 @expression 语法附加到类、方法、属性等元素上,用于动态修改其行为或添加元数据
启用装饰器
在 tsconfig.json 中开启实验性支持:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true // 支持反射元数据
}
}
类装饰器
-
作用: 修改类构造函数或原型,添加元数据或扩展功能。
-
示例: 为类添加属性或密封类定义:
function Sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @Sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } }
方法装饰器
-
作用: 拦截方法调用,添加日志、缓存、限流等功能。
-
示例: 记录方法执行时间:
function timing(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const start = performance.now(); const result = originalMethod.apply(this, args); console.log(`${key} 执行耗时:${performance.now() - start}ms`); return result; }; } class Timer { @timing slowOperation() { /* 耗时逻辑 */ } }
属性装饰器
-
作用: 动态初始化属性或添加元数据。
-
示例: 自动绑定方法的
this上下文:function autobind(target: any, propertyKey: string) { const descriptor: PropertyDescriptor = { get() { return this[propertyKey].bind(this); } }; Object.defineProperty(target, propertyKey, descriptor); } class Button { @autobind handleClick() { console.log(this); } }
参数装饰器
- 作用: 验证参数或记录参数信息。
- 示例: 强制参数为正数:
function positive(target: any, key: string, index: number) { const originalMethod = target[key]; target[key] = function (...args: any[]) { if (args[index] < 0) throw new Error("参数必须为正数"); return originalMethod.apply(this, args); }; } class Calculator { add(@positive a: number, b: number) { return a + b; } }
访问器装饰器
- 作用: 控制属性的读写权限。
- 示例: 设置只读属性:
function ReadOnly(target: any, key: string, descriptor: PropertyDescriptor){ descriptor.writable = false; } class Circle { private _radius: number; @ReadOnly get radius() { return this._radius; } }
注意事项
-
执行顺序
类内部装饰器(属性→方法→参数)先于类装饰器执行。
多个装饰器按从下到上顺序执行 -
潜在风险
实验性特性:装饰器尚未成为 ECMAScript 标准,未来语法可能变化。
滥用风险:过度使用会降低代码可读性,需结合具体场景权衡。
总结
TypeScript 装饰器通过声明式语法实现代码增强,适用于日志、验证、元数据管理等场景。合理使用可提升代码复用性,但需注意其实验性状态和潜在维护成本。实际开发中可结合框架(如 NestJS、TypeORM)和工具库(如 reflect-metadata)充分发挥其价值。
九、TypeScript 循环遍历
遍历注意事项
- 类型安全: 在遍历对象时,建议使用
key in obj和hasOwnProperty检查避免原型污染。 - 性能:
for循环通常性能最佳,forEach次之,for...in最慢(需类型检查)。 - 不可变数据: 遍历时如需修改数据,注意使用
map/filter返回新数组而非直接修改原数据。
选择合适的循环遍历方法
- 需要索引且可以中断: for 循环是最佳选择。
- 只需要元素值且可以中断: for...of 循环是最佳选择。
- 对每个元素执行操作且不需要返回值(副作用): forEach() 是首选。
- 需要将数组转换为另一个新数组: map() 是首选。
- 需要从数组中筛选出符合条件的元素生成新数组: filter() 是首选。
- 需要将数组归纳为单个值(求和、计数、扁平化、分组等): reduce() 是最佳选择。
- 需要检查数组中是否存在满足条件的元素(或所有元素是否都满足条件),并可以提前终止: some() 或 every() 是最佳选择。
1、for 循环
遍历目标: 对于大规模数据遍历(如数组、类数组),for 循环是最佳选择。for 循环的性能通常优于 for...of 或高阶函数(如 map、forEach),它直接操作内存地址(索引),避免了for...of的迭代器开销
适用场景: 高性能需求、需要索引的场景,
返回值: 索引(i)
特点: 支持 break、continue 和 return,控制流程更灵活。
const arr: number[] = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 输出: 1, 2, 3
}
2、for...of 循环
遍历目标: 用于遍历可迭代对象(如数组、字符串、Map、Set 等)的值。它是一种简洁、现代的遍历方式避免了传统for循环的索引操作或 for...in 的原型链问题,性能较好。
适用场景: for...of 循环可以使代码更简洁易读,遍历无需索引,适合需要值的场景。
返回值: 值(value)
特点: 支持 break 和 continue,控制流程更灵活,且可以与 async/await 结合使用,方便地处理异步操作
const arr: number[] = [1, 2, 3];
for (const value of arr) {
console.log(value); // 输出: 1, 2, 3
}
3、for...in 循环
遍历目标:
- 主要用于遍历对象的属性键(包括自身属性和继承属性,它会遍历对象原型链上的可枚举属性)
1、如不需用到从原型链继承的属性可以用hasOwnProperty过滤
2、hasOwnProperty 可以检查属性是否为对象自身所有 for...in不推荐用于数组遍历
1、遍历顺序不固定(非索引顺序)
2、若对象原型链被修改(如Object.prototype添加方法),for...in会遍历到这些新增属性
3、索引类型为字符串(如"0"而非数字0)))
适用场景: 适用于需要获取对象自身及其原型链上的所有可枚举属性键(键名为字符串类型)的场景。(性能较差)
返回值: 值(value)
const obj = { a: 1, b: '2', c: true };
/**
* 1. for...in 循环 案例1
*
* Object.prototype.hasOwnProperty.call(obj, key)其作用是:
* 通过调用 Object 原型上的原生 hasOwnProperty 方法,绕过对象自身可能被覆盖的 hasOwnProperty 实现,
* 从而准确判断 obj 是否直接包含 key 属性(即直接定义在对象上的属性,而非从原型链继承的属性)
**/
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key as keyof typeof obj]; // 类型安全访问
console.log(`key: ${key}, Value: ${value}`); //输出: key: a, Value: 1 , key: b, Value: 2,key: c, Value: true
}
}
/**
* 2. for...in 循环,过滤原型链属性 案例2
*
* hasOwnProperty 是 JavaScript 中用于检查对象是否具有某个自身属性(而非继承自原型链的属性)的方法。它的核心用途是避免在遍历对象属性时意外访问到原型链上的属性
* hasOwnProperty 返回一个布尔值,表示对象是否直接拥有指定的属性(而非从原型链继承)
* 在 for...in 循环中,默认会遍历对象及其原型链上的所有可枚举属性。通过 hasOwnProperty 可以过滤掉继承的属性
**/
const person = {
name: 'Alice',
age: 25,
};
// 检查对象自身属性
console.log(person.hasOwnProperty('name')); // 输出:true
console.log(person.hasOwnProperty('age')); // 输出:true
console.log(person.hasOwnProperty('toString')); // 输出:false(因为toString 继承自原型链。toString 是 Object.prototype 上的方法,所有对象(包括 person)默认继承自 Object.prototype)
for (const key in person) {
if (person.hasOwnProperty(key)) {
const value = person[key as keyof typeof person]; // key需要显式类型断言为person的属性
console.log(`${key}: ${value}}`); // 仅输出自身属性
}
}
/**
* 2. for...in 循环,过滤原型链属性 案例3
*
**/
function Parent() {}
Parent.prototype.sharedMethod = function() {}; //从原型链上添加一个sharedMethod 函数
const child = new Parent();
child.name = 'Child';
// 遍历对象属性(包括原型链)
for (const key in child) {
console.log(key); // 输出: name, sharedMethod
}
// 安全遍历:仅输出自身属性
for (const key in child) {
if (child.hasOwnProperty(key)) {
console.log(key); // 输出: name
}
}
4、forEach 遍历
遍历目标: forEach 方法的主要遍历目标是数组。它适用于任何类型的数组,包括:
- 基本类型数组: 如
number[],string[],boolean[]等。 - 对象数组: 如
Person[],Product[]等,其中每个元素都是一个对象。 - 混合类型数组: 虽然不常见,但 TypeScript 的元组类型允许数组中包含不同类型的元素
适用场景: forEach 适用于以下场景:
- 对每个元素执行副作用操作: 当你需要对数组中的每个元素执行某种操作,并且不关心返回值时,forEach 是一个不错的选择。例如,更新 DOM 元素、发送网络请求、记录日志等。
- 不需要返回值: 如果你只是需要遍历数组并对每个元素进行处理,而不需要生成新的数组,forEach 比 map 更合适,因为 map 会返回一个新数组。
- 简单遍历逻辑: 当遍历逻辑相对简单,不需要复杂的条件判断或中断遍历时,forEach 可以使代码更简洁。
- 类型安全的操作: TypeScript 会对 forEach 的回调函数进行类型检查,确保操作是类型安全的。这对于处理对象数组特别有用。
不适用场景: 虽然 forEach 在很多情况下非常有用,但在以下场景中可能不是最佳选择:
- 需要提前终止遍历: forEach 无法中途中断遍历。如果需要提前终止遍历,可以使用普通的 for 循环或 for...of 循环配合 break 语句。
- 需要异步操作: forEach 不会等待异步操作完成。如果需要在 forEach 中执行异步操作,考虑使用 for...of 循环配合 await
- 需要返回新数组: 如果需要返回一个新数组,请使用 map 方法
返回值: 没有返回值
语法:
/**
* callback:它是一个回调函数,会为数组中的每个元素执行一次,接收以下参数:
* currentValue:当前处理的元素值
* index(可选):当前元素的索引
* array(可选):被遍历的数组本身
* thisArg(可选):用于在执行 callback 函数时,将其作为 this 关键字的值(通常很少使用)
*
**/
array.forEach(callback(currentValue, index, array), thisArg);
经典案例:
案例1:遍历数组并打印元素
const fruits: string[] = ['apple', 'banana', 'cherry'];
fruits.forEach((fruit) => {
console.log(fruit);
});
案例2:更新对象数组中的属性
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: 'Laptop', price: 999.99 },
{ id: 2, name: 'Phone', price: 499.99 }
];
// 为每个产品增加 10% 的价格
products.forEach((product) => {
product.price *= 1.1; // 增加 10%
});
console.log(products);
// 输出:
// [
// { id: 1, name: 'Laptop', price: 1099.989 },
// { id: 2, name: 'Phone', price: 549.989 }
// ]
案例3: 操作 DOM 元素
const buttons: HTMLButtonElement[] = [...document.querySelectorAll('button')];
buttons.forEach((button) => {
button.addEventListener('click', () => {
console.log('Button clicked!');
});
});
案例4:遍历数组并执行异步操作
虽然 forEach 本身不支持异步操作,但你可以在 forEach 中调用异步函数。注意,forEach 不会等待异步操作完成。
const urls: string[] = ['https://example.com/1', 'https://example.com/2', 'https://example.com/3'];
const fetchData = async (url: string) => {
const response = await fetch(url);
const data = await response.json();
console.log(data);
};
urls.forEach((url) => {
fetchData(url);
});
案例5:遍历 Map 对象
虽然 forEach 主要用于数组,但也可以用于其他可迭代对象,比如 Map
const myMap = new Map<string, number>([['a', 1], ['b', 2], ['c', 3]]);
myMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 输出:
// a: 1
// b: 2
// c: 3
5、Map 方法(返回新数组)
简介:
Map 是一种键值对集合,键可以是任意类型(包括对象、函数等),而普通对象的键只能是字符串或 Symbol
遍历目标:
map 方法的主要遍历目标是数组。它可以用于任何类型的数组,包括:
- 基本类型数组: 如 number[], string[], boolean[] 等。
- 对象数组: 如 Person[], Product[] 等,其中每个元素都是一个对象。
- 混合类型数组: 虽然不常见,但 TypeScript 的元组类型允许数组中包含不同类型的元素。
适用场景:
- 生成新数组: 当你需要基于原数组生成一个新的数组,并且新数组的元素是通过对原数组元素进行某种变换得到时,map 是一个理想的选择
- 数据转换: 当需要对数组中的每个元素进行某种数据转换时,map 可以非常方便地实现。例如,将对象数组中的某些属性提取出来形成新数组
- 类型转换: map 可以用于类型转换,例如将一个类型的数组转换为另一个类型的数组
- 返回复杂对象: map 可以用于返回更复杂的对象,而不仅仅是基本类型。
注意事项:
- 不可变性: map 方法不会改变原数组,而是返回一个新数组。这符合函数式编程的不可变性原则。
- 类型安全: TypeScript 会对 map 的回调函数进行类型检查,确保操作是类型安全的。
- 性能: 对于大型数组,map 方法可能会消耗较多的内存,因为它会创建一个新的数组。
语法:
const map = new Map<KeyType, ValueType>();
经典案例:
案例1:生成新数组
const numbers: number[] = [1, 2, 3, 4, 5];
const doubledNumbers: number[] = numbers.map((number) => number * 2);
console.log(doubledNumbers); // 输出: [2, 4, 6, 8, 10]
案例2:数据转换
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 1, name: 'Laptop', price: 999.99 },
{ id: 2, name: 'Phone', price: 499.99 }
];
const productNames: string[] = products.map((product) => product.name);
console.log(productNames); // 输出: ['Laptop', 'Phone']
案例3:类型转换
const numbers: number[] = [1, 2, 3, 4, 5];
const numbersAsString: string[] = numbers.map((number) => number.toString());
console.log(numbersAsString); // 输出: ['1', '2', '3', '4', '5']
案例4:返回复杂对象
const productsWithDiscount: Product[] = products.map((product) => ({
...product,
price: product.price * 0.9 // 打九折
}));
console.log(productsWithDiscount);
// 输出:
// [
// { id: 1, name: 'Laptop', price: 899.991 },
// { id: 2, name: 'Phone', price: 449.991 }
// ]
6、Set方法(唯一值集合)
介绍:
在 TypeScript 中,Set 是一种集合数据结构,用于存储唯一值。遍历 Set 的目标是访问集合中的每一个元素。Set 的遍历适用于需要操作或检查唯一值的场景。以下是 Set 遍历的遍历目标与适用场景的详细说明:
遍历目标:
- 访问唯一值: Set 中的每个元素都是唯一的,遍历的目标是访问这些唯一的值。
- 不关心索引: 与数组不同,Set 中的元素没有索引,遍历时不需要考虑索引。
- 顺序性: 虽然 Set 不保证元素的插入顺序(在某些实现中可能保持顺序,如 ES6 的 Set),但遍历时通常按照插入顺序进行。
适用场景:
- 去重操作后的遍历: 当需要从一个数组中去除重复值并遍历唯一值时,可以先将数组转换为 Set,然后遍历 Set
- 快速查找: 如果需要频繁检查某个值是否存在于集合中,可以先将值存储在 Set 中,然后遍历 Set 进行相关操作。
- 集合运算: 在实现集合运算(如并集、交集、差集)时,可能需要遍历 Set 中的元素
- 缓存或去重后的数据处理: 在处理大量数据时,可能需要先去重,然后对唯一值进行操作(如统计、转换等)
- 事件监听或回调管理: 在某些场景中,可能需要管理一组唯一的回调函数或事件监听器,Set 可以用来存储这些唯一项,并在需要时遍历执行
语法:
const set = new Set<ValueType>();
经典案例:
案例1:去重操作后的遍历
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = new Set(numbers);
uniqueNumbers.forEach(number => {
console.log(number); // 输出: 1, 2, 3, 4, 5
});
案例2:快速查找
const allowedValues = new Set<string>(['apple', 'banana', 'cherry']);
allowedValues.forEach(value => {
console.log(`Allowed value: ${value}`);
});
案例3:集合运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 计算并集
const union = new Set([...setA, ...setB]);
union.forEach(value => {
console.log(value); // 输出: 1, 2, 3, 4
});
案例4:缓存或去重后的数据处理
const words = ['hello', 'world', 'hello', 'typescript', 'world'];
const uniqueWords = new Set(words);
uniqueWords.forEach(word => {
console.log(word); // 输出: hello, world, typescript
});
案例5:事件监听或回调管理
const callbacks = new Set<() => void>();
callbacks.add(() => console.log('Callback 1'));
callbacks.add(() => console.log('Callback 2'));
callbacks.forEach(callback => {
callback(); // 输出: Callback 1, Callback 2
});
7、while 循环
使用场景: 当循环的次数在开始时无法确定,但可以根据某个条件来判断是否继续循环时,while 循环非常适用,如: while (true) { ... }
案例1:
// 要求用户输入一个有效的数字。如果用户输入无效(例如,输入了非数字字符),
// 提示用户重新输入,直到用户输入一个有效的数字。
function processNumbers(target: number): number {
let numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let index: number = 0;
let sum: number = 0;
while (index < numbers.length) {
// 当遇到偶数时,continue 语句会跳过当前迭代,不累加偶数到 sum 中,直接进入下一次循环
if (numbers[index] % 2 === 0) {
index++;
continue;
}
// 如果找到目标数字 target,break 语句会立即退出 while 循环,不再继续检查数组中的其他元素
if (numbers[index] === target) {
console.log(`找到目标数字: ${numbers[index]}`);
break;
}
// 累加奇数的值
sum += numbers[index];
index++;
}
// 如果找到了目标数字并退出循环,return sum 会返回累加的和
if (index < numbers.length) {
return sum; // 返回累加和
} else {
console.log("未找到目标数字。");
return -1; // 如果没有找到目标数字,返回 -1
}
}
const result = processNumbers(5);
console.log(`函数返回值: ${result}`);
案例2:
let count = 0;
while (count < 5) {
console.log(`While 循环,计数:${count}`);
count++;
}
8、do while 循环
使用场景: do...while循环会至少执行一次循环体,即使循环条件在第一次检查时就不满足。
// 用户输入验证:确保用户输入有效数据,至少提供一次输入机会。
function getValidNumber(): number {
let userInput: string; // 用于存储用户的输入
let isValid: boolean = false; // 是一个布尔值,用于跟踪用户输入是否有效
let number: number; // 用于存储转换后的数字
do {
userInput = prompt("请输入一个有效的数字:");
number = Number(userInput);
if (isNaN(number)) {
console.log("输入无效,请输入一个有效的数字。");
} else {
isValid = true; // 如果输入有效,isValid 被设置为 true
}
} while (!isValid);
return number;
}
const validNumber = getValidNumber();
console.log(`你输入的有效数字是: ${validNumber}`);
9、filter 过滤
filter() 方法是数组对象的一个内置方法,用于类型安全的元素筛选,它不会改变原数组,而是返回一个新数组。
基本语法:
/**
* callback:它是一个回调函数,会为数组中的每个元素执行一次。它的返回值决定了当前元素是否应该被包含在新数组中。它接受以下参数:
* element:当前正在处理的元素
* index(可选):当前元素的索引。
* array(可选):调用 filter() 的数组。
*
* thisArg(可选):这个可选参数用于在执行 callback 函数时,将其作为 this 关键字的值。
**/
const newArray = array.filter(callback(element, index, array), thisArg?)
案例1:过滤数字数组
const numbers = [1, 2, 3, 4, 5, 6];
// 过滤出偶数
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4, 6]
案例2:过滤对象数组
interface Person {
name: string;
age: number;
}
const people: Person[] = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 20 },
{ name: "David", age: 35 }
];
// 过滤出年龄大于 25 的人
const adults = people.filter(person => person.age > 25);
console.log(adults);
// 输出: [{ name: "Bob", age: 30 }, { name: "David", age: 35 }]
案例3:使用索引
const numbers = [10, 20, 30, 40, 50];
// 过滤出索引为偶数的元素
const indexedNumbers = numbers.filter((num, index) => index % 2 === 0);
console.log(indexedNumbers); // 输出: [10, 30, 50]
10、reduce :遍历元素将其累积为单个值
简介:
reduce() 是数组(Array)的核心方法,用于通过遍历元素将其累积为单个值。
reduce() 是一个非常强大的数组方法,可以替代许多其他数组操作(如 map + filter 组合),通常性能也更优。
注意事项:
- 如果提供了 initialValue,reduce 会从第一个元素开始执行
- 如果没有提供 initialValue,reduce 会从第二个元素开始执行,使用第一个元素作为初始值
- 空数组调用 reduce 且没有提供初始值会抛出 TypeError
基本语法:
/**
* callback:必选的回调函数,接收以下参数:
* accumulator:累积值,初始为 initialValue 或数组第一个元素。
* currentValue:当前处理的元素。
* index(可选):当前索引。
* array(可选):原数组对象。
* initialValue(可选):初始累积值,推荐显式指定以确保类型安全
**/
array.reduce<ResultType>(
callback: (accumulator: ResultType, currentValue: T, index?: number, array?: T[]) => ResultType,
initialValue: ResultType
): ResultType;
经典案例:
案例1:
const numbers = [1, 2, 3, 4, 5];
const sum1 = numbers.reduce((acc, num) => acc + num);
console.log(sum1) // 输出15
const sum2 = numbers.reduce((acc, num) => acc + num, 100); // 最后的这个参数100是初始累积值,相当于100+1+2+3+4+5
console.log(sum2) // 输出:115
const product = numbers.reduce<number>((acc, num) => acc * num, 2); //最后的这个参数2是初始累积值,相当于2*1*2*3*4*5
console.log(product) // 输出:240
案例2:
const numbers = [10, 2, 33, 4, 15];
const max1 = numbers.reduce((acc, curr) => Math.max(acc, curr));
console.log(max1) // 输出: 33
/*
Infinity 是一个特殊的数值,表示正无穷大。它是一个全局属性,可以通过直接访问 Infinity 来使用。
-Infinity 被用作 reduce() 方法的初始值。这样做有几个原因:
1、确保正确性:当数组为空时,reduce() 方法如果没有提供初始值会抛出错误。使用 -Infinity 可以确保即使数组为空,reduce() 也能正常工作(虽然在这种情况下,Math.max(-Infinity) 仍然是 -Infinity)。
2、比较的起点:Math.max(acc, curr) 会在每次迭代中比较当前的累加器值 (acc) 和当前元素 (curr)。使用 -Infinity 作为初始值,可以确保任何数组元素都会比这个初始值大,从而保证第一次比较总是正确的。
3、数学上的正确性:-Infinity 是最小的可能数值,因此任何有限的数值都会比它大。这使得它成为寻找最大值的一个理想的初始值。
*/
const max2 = numbers.reduce((acc, curr) => Math.max(acc, curr), -Infinity);
console.log(max2) // 输出: 33
11、some() 和 every() 方法:检查数组中的元素是否满足某个条件
简介:
这两个方法都用于检查数组中的元素是否满足某个条件,并返回一个布尔值。
- some(): 如果数组中至少有一个元素通过了回调函数的测试,则返回 true。一旦找到满足条件的元素,就会立即停止遍历。
- every(): 如果数组中所有元素都通过了回调函数的测试,则返回 true。一旦找到不满足条件的元素,就会立即停止遍历。
优点:
- 提前终止: 效率高,因为一旦满足/不满足条件就停止。
- 语义清晰: 明确表达“是否存在某个元素满足条件”或“所有元素是否都满足条件”。
经典案例:
const ages: number[] = [18, 20, 22, 17, 25];
// 检查是否存在未成年人
const hasUnderage: boolean = ages.some(age => age < 18);
console.log(hasUnderage); // 输出: true
// 检查是否所有人都已成年
const allAdults: boolean = ages.every(age => age >= 18);
console.log(allAdults); // 输出: false
十、JavaScript 原生函数(TypeScript 继承)
find():返回第一个符合条件的元素值
未找到值的时候返回undefined
const numbers = [10, 2, 33, 4, 15];
let arr = numbers.find(itme => itme > 4)
console.log(arr) //输出:10
findIndex():返回第一个符合条件的元素索引
未找到值的时候返回-1
const numbers = [10, 2, 33, 4, 15];
let i = numbers.findIndex(itme => itme > 10)
console.log(i) //输出:2
Object.prototype.hasOwnProperty.call()
const obj = { a: 1, b: '2', c: true };
/**
* 1. for...in 循环
*
* Object.prototype.hasOwnProperty.call(obj, key)其作用是:
* 通过调用 Object 原型上的原生 hasOwnProperty 方法,绕过对象自身可能被覆盖的 hasOwnProperty 实现,
* 从而准确判断 obj 是否直接包含 key 属性(即直接定义在对象上的属性,而非从原型链继承的属性)
**/
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key as keyof typeof obj]; // 类型安全访问
console.log(key, value); // key: string, value: any (需类型断言)
}
}
Object.keys():返回对象自身可枚举属性名的数组
/**
* 2. Object.keys + forEach循环
*
* Object.keys(obj)获取obj对象的所有可枚举自有属性键,并以数组形式返回
* 返回值的示例:[ 'id', 'name', 'age' ]
**/
const obj = { id: 1, name: 'lily', age: 18 }
const keys = Object.keys(obj);
keys.forEach(key => {
console.log(key) //输出:id name age
});
Object.values():返回对象自身可枚举属性值的数组
/**
* 3. Object.values + forEach循环
*
* Object.values(obj)获取obj对象的所有可枚举自有属性值,
* 返回一个数组,包含对象自身所有可枚举属性(不包含继承属性)的值
* 返回值的示例:[ 1, 'lily', 18 ]
**/
const obj = { id: 1, name: 'lily', age: 18 }
Object.values(user).forEach((item ,index)=>{
console.log(item) //输出 1 lily 18
})
Object.entries():返回对象自身可枚举键值对的二维数组
/**
* 4. Object.entries + forEach循环
*
* Object.entries(obj)获取obj对象自身可枚举属性的键值对数组,
* 返回一个由对象 obj 的自身可枚举属性组成的二维数组,每个子数组包含两个元素:[键, 值]
* 返回值的示例:[["a", 1], ["b", "2"],["c",true]]
**/
Object.entries(obj).forEach(([key, value]) => {
console.log(key, value); // key: string, value: any
});
call()、apply()和bind()方法
在 TypeScript 中 Function.prototype.call() 和 Function.prototype.apply()和 Function.prototype.bind()都是非常常用的方法,它们的作用一模一样,区别仅在于传入参数形式的不同。
参数传递
- call(): 参数逐个传递(func.call(thisArg, arg1, arg2))
call传入的参数数量不固定,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数: - apply(): 参数以数组形式传递(func.apply(thisArg, [arg1, arg2]))
apply接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为 一个带下标的集合,这个集合可以为数组,也可能为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数 - bind(): 预设参数并返回新函数,调用时再补充剩余参数
执行时机
- call() , apply(): 立即执行函数。
- bind(): 返回新函数,需手动调用(适用于延迟执行或事件绑定)
const person = {
fullName: function(city, country) {
return this.firstName + " " + this.lastName + ", " + city + ", " + country;
}
};
const person1 = {
firstName: "John",
lastName: "Doe"
};
/*
call方法:逐个传递参数
第一个参数:改变 this 指向,通过第一个参数显式指定函数内部 this 绑定的对象
第二个参数:fullName函数的第一个参数
第三个参数:fullName函数的第二个参数
person.fullName.call(person1, "Seattle", "USA") 此时fullName函数内部的this指向的是person1这个对象,
所以person对象的fullName函数内部中的this.firstName其实就是person1.firstName
所以person对象的fullName函数内部中的this.lastName 其实就是person1.lastName
*/
const result1 = person.fullName.call(person1, "Seattle", "USA");
console.log(result1) //输出:John Doe, Seattle, USA
/*
apply方法:动态数组传递参数
第一个参数:改变 this 指向,通过第一个参数显式指定函数内部 this 绑定的对象
第二个参数:是一个动态数组。
数组的第一个值是fullName函数的第一个参数
数组的第二个值是fullName函数的第二个参数
person.fullName.call(person1, "Seattle", "USA") 此时fullName函数内部的this指向的是person1这个对象,
所以person对象的fullName函数内部中的this.firstName其实就是person1.firstName
所以person对象的fullName函数内部中的this.lastName 其实就是person1.lastName
*/
const result2 = person.fullName.apply(person1, ["Seattle", "USA"]);
console.log(result2) //输出:John Doe, Seattle, USA
/*
bind方法:
第一个参数:用于显式绑定函数的执行上下文(this 值)
第二个参数:(可选参数)如果指定值,则是fullName函数的第一个参数
第二个参数:(可选参数)如果指定值,则是fullName函数的第二个参数
person.fullName.bind(person1, "Seattle", "USA") 此时fullName函数内部的this指向的是person1这个对象,
所以person对象的fullName函数内部中的this.firstName其实就是person1.firstName
所以person对象的fullName函数内部中的this.lastName 其实就是person1.lastName
bind方法返回一个可调用的新函数
*/
const result3 = person.fullName.bind(person1, "Seattle", "USA")
console.log(result3())//输出:John Doe, Seattle, USA
//只绑定函数的执行上下文(this 值)为person1
const result4 = person.fullName.bind(person1)
//因为person.fullName.bind(person1) 没有传递fullName函数的第一个和第二个参数,所以在执行bind方法返回的新函数的时候就需要传值了。
console.log(result3("SeattleA", "USA"))//输出:John Doe, Seattle, USA
//绑定函数的执行上下文(this 值)为person1,和预设了fullName函数的第一个参数为"SeattleA"
const result5 = person.fullName.bind(person1, "Seattle")
//因为person.fullName.bind(person1, "Seattle") 没有传递fullName函数的第二个参数,所以在执行bind方法返回的新函数的时候就需要传第二个值了。
console.log(result3("USA"))//输出:John Doe, Seattle, USA
//绑定函数的执行上下文(this 值)为person1,和预设了fullName函数的第一个参数为"SeattleA",和预设了第二个参数为"USA"
const result6 = person.fullName.bind(person1, "Seattle", "USA")
//因为person.fullName.bind(person1, "Seattle", "USA") 传递fullName函数的第一,第二个参数,所以在执行bind方法返回的新函数的时候就不需要传值了。
console.log(result3()) //输出:John Doe, Seattle, USA
参数传递方式对比
| 方法 | 参数形式 | 适用场景 | TypeScript 类型示例 |
|---|---|---|---|
| call() | arg1, arg2, ... | 参数数量固定时 | func.call(thisArg, "a", 1) |
| apply() | [arg1, arg2] | 参数动态或已是数组(如 arguments) | func.apply(thisArg, ["a", 1]) |
| bind() | 预设部分参数,返回新函数 | 需要多次调用相同上下文 | const bound = func.bind(thisArg, "a"); bound(1); |
Date() 日期
//创建 Date 对象
const date = new Date();
//获取日期和时间部分
console.log(date.getFullYear()); // 年份
console.log(date.getMonth()); // 月份 (0-11)
console.log(date.getDate()); // 日 (1-31)
console.log(date.getDay()); // 星期几 (0-6, 0是周日)
console.log(date.getHours()); // 小时
console.log(date.getMinutes()); // 分钟
console.log(date.getSeconds()); // 秒
console.log(date.getMilliseconds()); // 毫秒
console.log(date.getTime()); // 时间戳 (毫秒)
//设置日期和时间
date.setFullYear(2024);
date.setMonth(5); // 6月
date.setDate(20);
date.setHours(15);
date.setMinutes(45);
date.setSeconds(30);
// 本地化日期时间字符串
console.log(date.toString()); // "Thu Jun 15 2023 14:30:00 GMT+0800 (中国标准时间)"
console.log(date.toLocaleString()); // "2023/6/15 14:30:00" (根据本地设置)
console.log(date.toLocaleDateString()); // "2023/6/15"
console.log(date.toLocaleTimeString()); // "14:30:00"
// 自定义本地化选项
console.log(date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
})); // "2023/06/15"
date-fns日期第三方库
对于更复杂的格式化需求,可以使用第三方库如 date-fns 或 moment.js( moment.js 现在已不推荐用于新项目):
安装date-fns
- 安装软件包
#安装 date-fns 软件包 npm install date-fns --save # or yarn add date-fns
一、日期格式化函数
将日期转换为可读字符串或相对时间描述。
-
format(date, formatString, [options]): 将日期格式化为指定字符串(如 'yyyy-MM-dd HH:mm:ss')
import{ format } from 'date-fns' //format 函数会将传入的 Date 对象视为 本地时间(UTC+8),并按照本地时区进行格式化 format(new Date(), "yyyy-MM-dd"); // "2025-06-10" format(new Date(), "MMMM do, yyyy", { locale: zhCN }); // "六月 10日, 2025"(需导入中文locale) -
formatDistance(date, baseDate, [options]): 计算两个日期的相对时间差(如 "2天前")
import{ formatDistance,subDays } from 'date-fns' import { zhCN } from "date-fns/locale"; formatDistance(subDays(new Date(), 3), new Date(), { locale: zhCN }); // "3天前" const date = new Date(2025, 5, 5); // 2025-06-05 const baseDate = new Date(2025, 5, 10); // 2025-06-10 formatDistance(date, baseDate, { locale: zhCN }); // "5天" // 添加后缀 formatDistance(date, baseDate, { locale: zhCN, addSuffix: true }); // "5天前" formatDistance( new Date(2025, 5, 10, 14, 29, 30), // 2025-06-10 14:29:30 new Date(2025, 5, 10, 14, 30, 0), // 2025-06-10 14:30:00 { locale: zhCN, //指定输出文本的语言环境 zhCN 表示简体中文。例如输出如:“30秒”或“1分钟”。 includeSeconds: true, //当时间差小于 1 分钟时,是否显示秒数。默认行为:false(时间差 <1 分钟时显示“不到1分钟”)。 addSuffix: true //在输出结果后添加“前”或“后”,明确时间方向(过去或未来)。例如:若 dateA 在 dateB 之前 → 添加“前”(如 “30秒前”)。若 dateA 在 dateB 之后 → 添加“后”(如 “30秒后”)。 ); //“半分钟前”封装工具函数
场景:统一处理多语言和业务默认配置。
案例代码(参考真实项目封装)import { formatDistance } from 'date-fns'; import { zhCN, enUS } from 'date-fns/locale'; // 封装多语言时间差工具 export const formatDateDistance = (date, baseDate = new Date(), options = {}) => { const lang = options.lang || 'zh-CN'; const locales = { 'zh-CN': zhCN, 'en': enUS }; return formatDistance(date, baseDate, { locale: locales[lang], addSuffix: true, includeSeconds: false, ...options // 允许覆盖默认配置 }); }; // 调用示例 console.log(formatDateDistance( new Date(2025, 5, 10), new Date(2025, 5, 15), { lang: 'zh-CN' } )); // 输出:"5天前"
二、日期比较与验证函数
用于判断日期的关系、有效性或特定状态
-
isEqual(date1, date2): 判断两个日期是否严格相等(精确到毫秒)
import{ isEqual } from 'date-fns' isEqual(new Date(2025, 5, 10), new Date(2025, 5, 10)); // true -
isAfter(date, dateToCompare): 检查第一个日期是否在第二个日期之后
import{ isAfter } from 'date-fns' isAfter(new Date(), new Date(2024, 0, 1)); // true(当前日期是2025-06-10晚于2024-01-01) -
isBefore(date, dateToCompare): 检查第一个日期是否在第二个日期之前
import{ isBefore } from 'date-fns' isBefore(new Date(), new Date(2024, 0, 1)); // false(当前日期是2025-06-10晚于2024-01-01) -
isToday(date): 判读日期是否为今天
import{ isToday } from 'date-fns' isToday(new Date()); // true -
isYesterday(date): 判断日期是否为昨天(当前日期的前一天)
import{ isYesterday } from 'date-fns' isYesterday(new Date()); // false -
isTomorrow(date): 判断日期是否为明天(当前日期的后一天)
import{ isTomorrow } from 'date-fns' isTomorrow(new Date()); // false -
isExists(year, month, day): 验证指定年、月、日组合是否有效(例如检查 2023-02-30 是否合法)
-
isValid(date): 检查日期对象或时间戳是否为有效日期(如非法日期返回 false)
import{ isValid } from 'date-fns' isValid(new Date("无效日期")); // false -
isFriday(date): 判断日期是否为星期五
import{ isFriday } from 'date-fns' isFriday(new Date(2025, 5, 13)); // true(2025-06-13是周五) -
isWednesday(date): 判断日期是否为星期三
import{ isWednesday } from 'date-fns' isWednesday(new Date(2025, 5, 13)); // false(2025-06-13是周五)
三、时间添加函数
在给定日期上增加指定时间单位。
- addYears(date, amount): 添加指定年数(如 addYears(now, 1) 返回明年今日)
//当前日期添加1年 import{ format,addYears} from 'date-fns' format(addYears(new Date(2025, 5, 10), 1), "yyyy-MM-dd HH:mm:ss")//2026-06-10 14:15:59 - addMonths(date, amount): 添加指定月数(自动处理月末溢出,如 1月31日加1个月返回2月28日)
//当前日期添加1个月 import{ format,addMonths } from 'date-fns' format(addMonths(new Date(2025, 5, 10), 1), "yyyy-MM-dd HH:mm:ss")//输出:2025-07-10 14:15:59 - addDays(date, amount): 添加指定天数
//当前日期添加1天 import{ format,addDays } from 'date-fns' format(addDays(new Date(2025, 5, 10), 1), "yyyy-MM-dd HH:mm:ss")//输出:2025-06-11 14:15:59 - addHours(date, amount): 添加指定小时数
//当前日期添加1个小时 import{ format,addHours } from 'date-fns' format(addHours(new Date(2025, 5, 10, 14, 15, 59, 680), 1), "yyyy-MM-dd HH:mm:ss") //输出:2025-06-10 15:15:59 - addMinutes(date, amount): 添加指定分钟数
//当前日期添加1分钟 import{ format,addMinutes } from 'date-fns' format(addMinutes(new Date(2025, 5, 10, 14, 15, 59, 680), 1), "yyyy-MM-dd HH:mm:ss")//输出:2025-06-10 14:16:59 - addSeconds(date, amount): 添加指定秒数
//当前日期添加1秒钟 import{ format,addSeconds } from 'date-fns' format(addSeconds(new Date(2025, 5, 10, 14, 15, 59, 680), 1), "yyyy-MM-dd HH:mm:ss")//输出:2025-06-10 14:16:00 - addMilliseconds(date, amount): 添加指定毫秒数
//当前日期添加1毫秒 import{ format,addMilliseconds } from 'date-fns' format(addMilliseconds(new Date(2025, 5, 10, 14, 15, 59, 680), 1), "yyyy-MM-dd HH:mm:ss.SSS")//输出:2025-06-10 14:15:59.681 - addWeeks(date, amount): 添加指定周数(等同于 addDays(date, amount * 7))
//当前日期添加1周 import{ format,addWeeks } from 'date-fns' format(addWeeks(new Date(2025, 5, 10, 14, 15, 59, 680), 1), "yyyy-MM-dd HH:mm:ss")//输出:2025-06-17 14:15:59
四、时间减去函数
在给定日期上减少指定时间单位。
-
subMonths(date, amount): 减去指定月数
import{ format,subMonths } from 'date-fns' //当前时间为2025年6月13日 00:23:06 console.log(format(subMonths(new Date(), 1),"yyyy-MM-dd HH:mm:ss")); // 输出:2025-05-13 00:23:06 -
subDays(date, amount): 减去指定天数
import{ format,subDays } from 'date-fns' //当前时间是:2025年6月13日 00:29:55 console.log(format(subDays(new Date(), 1), "yyyy-MM-dd HH:mm:ss")); //输出:2025-06-12 00:29:55 -
subHours(date, amount): 减去指定小时数
import{ format,subHours } from 'date-fns' //当前时间是:2025年6月13日 00:29:55 console.log(format(subHours(new Date(), 1), "yyyy-MM-dd HH:mm:ss")); //输出:2025-06-12 23:29:55 -
subMinutes(date, amount): 减去指定分钟数
import{ format,subMinutes } from 'date-fns' //当前时间是:2025年6月13日 00:29:55 console.log(format(subMinutes(new Date(), 1), "yyyy-MM-dd HH:mm:ss")); //输出:2025-06-13 00:28:55 -
subWeeks(date, amount): 减去指定周数(等同于 subDays(date, amount * 7))
import{ format,subWeeks } from 'date-fns' //当前时间是:2025年6月13日 00:29:55 console.log(format(subWeeks(new Date(), 1), "yyyy-MM-dd HH:mm:ss")); //输出:2025-06-06 00:29:55
五、时间差异计算函数
计算两个日期之间的差值。
- differenceInYears(dateLeft, dateRight):计算两个日期之间相差的完整年数(忽略月、日)
注意: 若 dateRight 早于 dateLeft,返回负值//differenceInYears(dateLeft: Date | number, dateRight: Date | number): number import { differenceInYears } from 'date-fns'; const date1 = new Date(2025, 0, 1); // 2025-01-01 const date2 = new Date(2030, 0, 1); // 2030-01-01 const years = differenceInYears(date2, date1); // 5 - differenceInQuarters(dateLeft, dateRight): 计算两个日期之间相差的完整季度数(1季度=3个月)
注意: 结果向下取整,忽略不足一季度的天数//differenceInQuarters(dateLeft: Date | number, dateRight: Date | number): number import { differenceInQuarters } from 'date-fns'; const date1 = new Date(2025, 0, 1); // 2025-Q1 const date2 = new Date(2026, 3, 1); // 2026-Q2 const quarters = differenceInQuarters(date2, date1); // 5(2025Q1→2026Q2共5个季度) - differenceInMonths(dateLeft, dateRight): 计算两个日期之间相差的完整月数(忽略日)
注意: 结果基于日历月,不考虑月份天数差异//differenceInMonths(dateLeft: Date | number, dateRight: Date | number): number import { differenceInMonths } from 'date-fns'; const date1 = new Date(2025, 0, 15); // 2025-01-15 const date2 = new Date(2025, 5, 10); // 2025-06-10 const months = differenceInMonths(date2, date1); // 4(1月→6月,但不足5个月) - differenceInWeeks(dateLeft, dateRight): 计算两个日期之间相差的完整周数(7天为1周)
应用场景: 常用于生成年度周列表(如日历控件)//differenceInWeeks(dateLeft: Date | number, dateRight: Date | number): number import { differenceInWeeks } from 'date-fns'; const date1 = new Date(2025, 0, 1); const date2 = new Date(2025, 0, 22); // 21天后 const weeks = differenceInWeeks(date2, date1); // 3(21÷7=3) - differenceInDays(dateLeft, dateRight): 计算两个日期之间相差的整天数(24小时为1天)
注意: 结果按24小时制向下取整//differenceInDays(dateLeft: Date | number, dateRight: Date | number): number import { differenceInDays } from 'date-fns'; const date1 = new Date(2025, 0, 1, 12); // 1月1日中午 const date2 = new Date(2025, 0, 3, 6); // 1月3日早晨 const days = differenceInDays(date2, date1); // 1(不足48小时按1天算) - differenceInHours(dateLeft, dateRight): 计算两个日期之间相差的整小时数
//differenceInHours(dateLeft: Date | number, dateRight: Date | number): number import { differenceInHours} from 'date-fns'; const date1 = new Date(2025, 0, 1, 8, 30); const date2 = new Date(2025, 0, 1, 14, 45); const hours = differenceInHours(date2, date1); // 6(14:45 - 8:30 = 6小时15分,取整为6) - differenceInMinutes(dateLeft, dateRight): 计算两个日期之间相差的整分钟数。
//differenceInMinutes(dateLeft: Date | number, dateRight: Date | number): number import { differenceInMinutes } from 'date-fns'; const date1 = new Date(2025, 0, 1, 12, 0); const date2 = new Date(2025, 0, 1, 12, 45, 30); const minutes = differenceInMinutes(date2, date1); // 45 - differenceInSeconds(dateLeft, dateRight): 计算两个日期之间相差的整秒数。
应用场景: 倒计时、超时控制//differenceInSeconds(dateLeft: Date | number, dateRight: Date | number): number import { differenceInSeconds } from 'date-fns'; const date1 = new Date(2025, 0, 1, 0, 0, 0); const date2 = new Date(2025, 0, 1, 0, 1, 30); const seconds = differenceInSeconds(date2, date1); // 90 - differenceInMilliseconds(dateLeft, dateRight): 计算两个日期之间相差的毫秒数(最高精度)
注意: 常用于性能计时或高精度时间间隔//differenceInMilliseconds(dateLeft: Date | number, dateRight: Date | number): number import { differenceInMilliseconds } from 'date-fns'; const date1 = new Date(2025, 0, 1, 0, 0, 0, 0); const date2 = new Date(2025, 0, 1, 0, 0, 1, 500); const ms = differenceInMilliseconds(date2, date1); // 1500
六、时间范围函数
获取日期的开始或结束时间点。
- startOfDay(date): 返回指定日期的当天开始时间(00:00:00)
import{ format,startOfDay } from 'date-fns' //startOfDay是按 UTC 时间 计算的,而不是本地时间(UTC+8) //format默认是按 本地时间(UTC+8) 计算的,而不是UTC时间 //注意:月份是从0开始计算的,5表示6月 format(startOfDay(new Date(2025, 5, 10, 14, 15, 59, 680)), "yyyy-MM-dd HH:mm:ss.SSS") //输出:2025-06-10 00:00:00.000 - endOfDay(date): 返回指定日期的当天结束时间(23:59:59.999)
import{ format,endOfDay} from 'date-fns' //startOfDay是按 UTC 时间 计算的,而不是本地时间(UTC+8) //format默认是按 本地时间(UTC+8) 计算的,而不是UTC时间 //注意:月份是从0开始计算的,5表示6月 format(endOfDay(new Date(2025, 5, 10, 14, 15, 59, 680)), "yyyy-MM-dd HH:mm:ss.SSS")//输出:2025-06-10 23:59:59.999 - startOfMonth(date): 返回指定日期所在月份的起始时间(当月第一天 00:00:00)
import{ format,startOfMonth} from 'date-fns' //startOfMonth是按 UTC 时间 计算的,而不是本地时间(UTC+8) //format默认是按 本地时间(UTC+8) 计算的,而不是UTC时间 //注意:月份是从0开始计算的,5表示6月 format(startOfMonth(new Date(2025, 5, 10, 14, 15, 59, 680)), "yyyy-MM-dd HH:mm:ss.SSS")//输出:2025-06-01 00:00:00.000 - endOfMonth(date): 返回指定日期所在月份的结束时间(当月最后一天 23:59:59.999)
import{ format,endOfMonth} from 'date-fns' //endOfMonth是按 UTC 时间 计算的,而不是本地时间(UTC+8) //format默认是按 本地时间(UTC+8) 计算的,而不是UTC时间 //注意:月份是从0开始计算的,5表示6月 format(endOfMonth(new Date(2025, 5, 10, 14, 15, 59, 680)), "yyyy-MM-dd HH:mm:ss.SSS")//输出:2025-06-30 23:59:59.999
七、日期组件获取函数
从日期中提取特定时间单位的值。
-
getYear(date): 获取年份(四位数,如 2025)
import{ getYear } from 'date-fns' getYear(new Date(2025, 5, 10, 14, 15, 59)); //输出:2025注意:不同于原生 Date.getYear()(返回年份减1900),此函数返回完整年份
-
getMonth(date): 获取月份(0-11,0表示一月)
import{ getMonth } from 'date-fns' getMonth(new Date(2025, 5, 10, 14, 15, 59)); //输出:5 (注意这个5是指6月,月份是从0开始的) -
getDate(date): 获取日期在月份中的天数(1-31)
import{ getDate } from 'date-fns' //getDate(date: Date | number): number getDate(new Date(2025, 5, 10, 14, 15, 59)); //输出:10 -
getHours(date): 获取小时(0-23)
import{ getHours } from 'date-fns' getHours(new Date(2025, 5, 10, 14, 15, 59));//输出:14 -
getMinutes(date): 获取分钟(0-59)
import{ getMinutes } from 'date-fns' getMinutes(new Date(2025, 5, 10, 14, 15, 59));//输出:15 -
getSeconds(date): 获取秒数(0-59)
import{ getSeconds } from 'date-fns' getSeconds(new Date(2025, 5, 10, 14, 15, 59)); //输出:59 -
getMilliseconds(date): 获取毫秒数(0-999)
import{ getMilliseconds } from 'date-fns' getMilliseconds(new Date(2025, 5, 10, 14, 15, 59, 680)); //输出:680 -
getISOWeek(date): 获取日期在 ISO 周系统中的周数(1-53)
import{ getISOWeek } from 'date-fns' //getISOWeek(date: Date | number): number getISOWeek(new Date(2025, 5, 10)); // 输出:24
八、其他工具函数
辅助性日期转换或计算。
-
weeksToDays(weeks): 将周数转换为天数(如 weeksToDays(2) 返回 14)
import{ weeksToDays } from 'date-fns' weeksToDays(2); // 14 -
parse(dateString, formatString, baseDate): 解析字符串为日期对象,需指定格式
import{ parse } from 'date-fns' parse("2025-06-10", "yyyy-MM-dd", new Date()); // Date对象 ,如果日期解析失败则返回:Invalid Date -
parseISO(isoString): 解析 ISO 8601 格式字符串为日期对象(自动处理时区)
import{ parseISO } from 'date-fns' parseISO("2025-06-10T14:30:00Z"); // Date对象(UTC时间) -
max(datesArray): 返回日期数组中的最晚日期
案例1:import{ format,max} from 'date-fns' //在 JavaScript 中,new Date(2025, 0, 10) 创建的日期对象基于 本地时区(如北京时间 UTC+8) //本地时间:2025-01-10 00:00:00(北京时间)。 //转换为 UTC 时间:减去时区偏移(8小时)→ 2025-01-09T16:00:00.000Z const dates = [new Date(2025, 0, 10), new Date(2025, 5, 10)]; const maxDate = max(dates); //使用 format 函数将结果转换为本地时间字符串,避免 UTC 转换: format(maxDate, "yyyy-MM-dd HH:mm:ss"); // 输出:2025-06-10 00:00:00(本地时间)案例2:
import { parseISO, max, min, format } from "date-fns" //UTC 转本地时区: //当你用 format 输出时,date-fns 会将 UTC 时间的 00:00:00 转换为你的本地时区时间(比如 UTC+8), //因此: //"2025-01-12" → UTC 00:00:00 → 本地时间 08:00:00(UTC+8) //"2025-05-12" → UTC 00:00:00 → 本地时间 08:00:00(UTC+8) //为什么输出 08:00:00? //即使你只比较日期部分,max 仍然会返回完整的 Date 对象(包含时间部分)。由于你的本地时区是UTC+8,format 会将 UTC 的 00:00:00 显示为 08:00:00 //解决方法:将日期显式设置时间为同一时区。使用 parseISO 或 parse 函数时指定时区: const dates = ["2025-01-12", "2025-05-12"].map(d => parseISO(d)) //map(d => parseISO(d))表示将数组中的日期转换为UTC时间 const maxDate = max(dates); //使用 format 函数将结果转换为本地时间字符串,避免 UTC 转换: console.log(format(maxDate, "yyyy-MM-dd HH:mm:ss")); // 输出:2025-05-12 00:00:00 -
min(datesArray): 返回日期数组中的最早日期
案例1:import { format, max, parseISO } from "date-fns"; //在 JavaScript 中,new Date(2025, 0, 10) 创建的日期对象基于 本地时区(如北京时间 UTC+8) //本地时间:2025-01-10 00:00:00(北京时间)。 //转换为 UTC 时间:减去时区偏移(8小时)→ 2025-01-09T16:00:00.000Z const dates = [new Date(2025, 0, 10), new Date(2025, 5, 10)]; const minDate = min(dates); //使用 format 函数将结果转换为本地时间字符串,避免 UTC 转换: format(minDate, "yyyy-MM-dd HH:mm:ss"); // 输出:2025-01-10 00:00:00(本地时间)案例2:
import { parseISO, max, min, format } from "date-fns" //UTC 转本地时区: //当你用 format 输出时,date-fns 会将 UTC 时间的 00:00:00 转换为你的本地时区时间(比如 UTC+8), //因此: //"2025-01-12" → UTC 00:00:00 → 本地时间 08:00:00(UTC+8) //"2025-05-12" → UTC 00:00:00 → 本地时间 08:00:00(UTC+8) //为什么输出 08:00:00? //即使你只比较日期部分,max 仍然会返回完整的 Date 对象(包含时间部分)。由于你的本地时区是UTC+8,format 会将 UTC 的 00:00:00 显示为 08:00:00 //解决方法:将日期显式设置时间为同一时区。使用 parseISO 或 parse 函数时指定时区: const dates = ["2025-01-12", "2025-05-12"].map(d => parseISO(d)) //map(d => parseISO(d))表示将数组中的日期转换为UTC时间 const minDate = min(dates); //使用 format 函数将结果转换为本地时间字符串,避免 UTC 转换: console.log(format(minDate, "yyyy-MM-dd HH:mm:ss")); // 输出:2025-01-12 00:00:00
浙公网安备 33010602011771号