TypeScript教程
目录
- 1. TypeScript 简介
- 1.1. JavaScript
- 1.2. TypeScript
- 2. 编译TypeScript
- 2.1. 命令行编译
- 2.2. 自动化编译(推荐)
- 3.类型声明
- 4. 类型
- 4.1. 类型概括
- 4.2. any
- 4.3. unknown
- 4.4. never
- 4.5. void
- 4.6. object
- 4.7. tuple
- 4.8. enum
- 4.9. type
- 4.10. 特殊情况
- 4.11. class
- 4.12. 属性修饰符
- 4.13. 抽象类
- 4.14. interface(接口)
- 4.15. 一些相似概念的区别
- 5. 泛型
- 6. 类型声明文件
- 7. 装饰器
- 7.1. 类装饰器
- 7.2. 装饰器工厂
- 7.3. 装饰器组合
- 7.4. 属性装饰器
- 7.5. 方法装饰器
- 7.6. 访问器装饰器
- 7.7. 参数装饰器
1. TypeScript 简介
1.1. JavaScript
javaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入一些简单的逻辑,而且代码量很少。随着时间的推移,JavaScript 变得越来越流行,如今的 JavaScript 已经可以全栈编程了。现如今的 JavaScript 应用场景比当年丰富的多,代码量也比当年大很多,随便一个 JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行。然而 JavaScript 当年“出生简陋”,没考虑到如今的应用场景和代码量,逐渐出现很多困扰。
JavaScript 中的困扰:
1. 不清不楚的数据类型
let welcome = 'hello'
welcome() //编译器没有提示,TypeError: welcome is not a function
2. 有漏洞的逻辑
const str = Date.now() % 2 ? '奇数':'偶数'
if(str !== '奇数'){
alert('是偶数') //只会弹出这一个,编译器没有提示
}else if (str === '偶数') {
alert('是偶数')
}
3. 访问不存在的属性
const obj = {w:10,h:8}
const area = obj.w * obj.hh //编译器没有提示
4. 低级的拼写错误
const mes = 'hellow,world'
mes.toUperCase() /单词拼写错误,编译器没有提示
1.2. TypeScript
- TypeScript 由微软开发,是基于 JavaScript 的一个扩展语言。
- TypeScirpt 包含 JavaScript 的所有内容,即:TypeScript 是 JavaScript 的超集。
- TypeScript 增加了:静态类型检查、接口、泛型等很多现代开发特性,因此更适合在大型项目的开发。
- TypeScript 需要编译为 JavaScript,然后交给浏览器或其他 JavaScript运行环境执行。
静态类型检查:
在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常出现的几率,些种检查叫静态类型检查,即把运行时的错误前置。
同样功能,TypeScript 的代码量要大于 JavaScript,但 TypeScript 代码结构更加清晰,在后期代码维护中 TypeScript 却远胜于 JavaScript
2. 编译TypeScript
浏览器不能直接运行 TypeScript 代码,需要纊译为 JavaScript 再交由浏览器解析器执行
2.1. 命令行编译
要把 .ts 文件编译为 .js 文件,需要配置 TypeScript 的编译环境,步骤如下:
- 创建 demo.ts 文件,例如:
const person = {
name:"张三",
age:18
}
console.log(`我是${person.name},我今年${person.age}岁了`)
- 全局案装 TypeScript
npm i typescript -g
- 编译 .ts 文件
tsc demo.ts
2.2. 自动化编译(推荐)
- 创建 TypeScript 编译控制文件
tsc --init
# 1.工程中会生成 tsconfig.json配置文件,其中包含着很多编译时的配置。
# 2.观察发现,默认编译的 JS版本是ES7,可手动调整为其他版本
- 监视目录中的 .ts 文件变化
tsc --watch
- 优化,当编译出错时不生成 .ts 文件
tsc --noEmitOnError --watch
# 推荐在 tsconfig.json配置文件中改
3.类型声明
使用:对变量或函数形参,进行类型声明:
// 1. 具体类型
let a: string //字符串
let b: number //数值
let c: boolean //布尔值
//参数 x,y,函数返回值 必须是数值,
function demo(x: number, y: number): number {
return x + y
}
// 2. 字面量类型
let d: 'hello' //d的值只能为字符串“hello”
d = 'hello'
TS 会根据我们的代码,进行类型推导
let a = 100 //TS会推断变量a是数字,但是推荐编写类型声明
4. 类型
4.1. 类型概括
JavaScript 数据类型
string, number, boolean, null, undefined, bigint, symbol, object
备注:object 包含:Array, Function, Date, Error 等
TypeScript 数据类型
- 所有 JavaScript 类型
- 6个新类型:any, unknown, never, void, tuple, enum
- 2个自定义类型方式:type, interface
注意点
在 JavaScript 中的这些内置构造函数:Number, String, Boolean, 它们用于创建对就的包装对象,在日常开发时很少用,在 TypeScript 中也是同理,所以在 TypeScript 中进行类型声明时,通常都是用小写的 number, string, boolean
案例:
let str1: string
str1 = 'hello'
//str1 = new String('hello') //报错
let str2: String
str2 = 'hello'
console.log(str2)
console.log(typeof str1)
str2 = new String('hello')
console.log(typeof str2)
console.log(str2)
- 原始数据 VS 包装对象
- 原始数据:如 number, string, boolean, 在 JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快。
- 包装对象:如 Number对象、String对象、Boolean对象,是复杂类型,在内存中占用空间多,在日常开发时很少由开发人员自已创建包装对象。
- 自动装箱:JavaScript 在必要时会自动将原始类型包装成对象,以便调用方法和访问属性
// 原始类型数据
let str = "hello"
// 当访问 str.length 时,JavaScript 引擎做了以下工作
let size = (function() {
// 1.自动装箱:创建一个临时的 String对象 包装原始数据
let tempStringObject = new String(str)
// 2.访问 String对象 的length属性
let lengthValue = tempStringObject.length
// 3.销毁临时对象,返回长度(JavaScript引擎会自动处理对象销毁,开发者无感知)
return lengthValue
})()
console.log(size)
4.2. any
any:任意类型,一旦将变量类型限制为any,即放弃对该变量的类型检查
// 明确表示a类型为 any [显示any]
let a: any
a = 100
a = 'hello'
// 没有表示类型,推断为 any [隐示any]
let b
b = 100
b = 'hello'
// any类型的变量,可以赋值给任意类型的变量
let c: any
c = 9
let d: string
d = c
4.3. unknown
unknown:未知类型
- unknown 可以理解为一个类型安全的 any, 适用不确定数据的具体类型
- unknown 会强制开发者在使用之前进行类型检查,从而提供更强的类型安全。
- 读取 any 类型数据的任何属性都不会报错,而 unknown 正好与之相反。
// 设置a的类型为 unknown
let a: unknown
a = 100
a = 'hello'
let b: string
// b = a 不能将类型"unknown"分配给类型"string"
// 1.类型判断
if(typeof a === 'string'){
b = a
}
// 2.断言
b = a as string
b = <string> a
b.toUpperCase()//无警告
let c: any = "hello"
c.toUpperCase()//无警告
let d: unknown
// d.toUpperCase() //警告
(d as string).toUpperCase()
4.4. never
never:任何值都不是,就是不能有值,uedefined,null, “”, 0 都不行
- 几乎不用 never 去直接限制变量,因为没有意义。
- never 一般是 TypeScript 主动推断出来的
- never 也可用于限制函数的返回值
// 指定a的类型为never,即a以后不能存任何数据了
let a:never
// a = 1 //不能赋值
let b:string
b = 'hello'
if(typeof b === 'string'){
console.log(b.toUpperCase())
}else {
console.log(b) // TypeScript 会推断出此出的a是never,因为没有任何一个值符合此处逻辑
}
// 限制throwError函数需要任何返回值,任何值都不行
// 没有返回值函数,执行完默认返回 unddefined
function throwError(str: string):never{
throwError("程序异常退出"+str)
}
4.5. void
void 与 undefined
void 是一个广泛的概念,用来表达“空”,而 undefined 则是这种“空”的具体表现实现之一,因此可以说 undefined 是 void 能接受的“空”状态的一种具体形式。
也就是说:void 包含 undefined ,但 void 表达的语义超越了单纯的 undefined ,它是一种意图上的约定,而不仅仅是特定值的限定。
总结:
void 返回类型的 函数,虽然返回 undefined ,但无所谓,不应该管,不应该用。
//void 通常用于函数返回值声明,含义:函数不返回任何值,调用者也不应依赖其返回值进行任何操作
function logMessage(msg:string):void{
console.log(msg)
// return undefined // ok
// return; //ok
/*
没有 return ,函数没有 显式返回值,
但会有一个 隐式返回值:undefined;
即:虽然函数返回类型为void,但也可以接受 undefined.
*/
}
logMessage('hellohello')// 调用时,不应该接收返回值,就算接收了,这个返回值也不能用
function logMessage2(msg:string):undefined{
}
let res = logMessage2("hello") //调用时,可接收返回值,可使用
if(res){
}
4.6. object
object 与 Object 实际开发中不用,因为范围太大了
- object(小写):所有非原始数据,即对象,函数,数组等
- Object(大写):所有可以调用 Object 方法的类型,即除了 undefined、null
let a: object
a = {name:'张三'}
a = [1,2]
a = function(){}
a = new String('hello')
class Person{}
a = new Person()
let b: Object
b = 1
声明对象类型
//限制person对象必须有name属性,age为可选属性
let person: { name: string, age?: number }
//也可用分号
let person2: { name: string; age?: number }
//也可用换行
let person3: {
name: string
age?: number
}
person = { name: "张三", age: 18 }
person = { name: '张三' }
//索引签名:允许定对象可以具有任意数量的属性,这些属性的键和类型是可变的
let person4: {
name: string
age: number
[key: string]: any //索引签名,可以不用key,只要占位
}
person4 = { name: '张三',age: 18, gender: '男' }
声明函数类型
// TypeScript 中的 => 在函数类型声明时表示函数类型,描述其参数类型和返回类型
// JavaScript 中的 => 是一种定义函数的语法,是具体的函数实现
let count: (a:number,b:number) =>number //a,b 相当于占位符,可以是别的值
count = function(w,h){
return w*h
}
声明数组类型
let arr1: string[]
let arr2: Array<number> // 泛型
arr1 = ['a','b']
arr2 = [1,2]
4.7. tuple
元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同,元组用于精确描述一组值的类型
let arr1:[string,number]
let arr2:[string,number?]
let arr3:[string,...string[]] //...表示任意数量的字符串类型
arr1 = ['hello',100]
4.8. enum
枚举(enum)可以定一组命名常量,增强代码可读性
function walk(str:string){
if(str === 'up'){
console.log('up')
}else if(str === 'down'){
console.log('down')
}else {
console.log('other')
}
}
walk('down')//walk函数,参数是字符串,写错没有提示,up、down 是连续相关的一组值,适合使用 枚举(enum)
//改为
// 1.数字枚举
enum Direction {
Up,
Down
}
console.log(Direction) //{0: 'Up', 1: 'Down', Up: 0, Down: 1}
function walk2(data:Direction){
if(data === Direction.Up){
console.log('Up')
}else if(data == Direction.Down){
console.log('Down')
}else{
console.log('other')
}
}
walk2(Direction.Up)
// 2.字符串枚举
enum Direction2 {
Up = 'up',
Down = 'down'
}
console.log(Direction2) // {Up: 'up', Down: 'down'} 丢失反向映射
// 3.常量枚举
// 使用 Const 关键字定义,在编译时会被 内联,避免生成一些额外代码
// 内联:TypeScript 编译时,会将枚举成员引用替换成它们的实际值,减少代码量
const enum Direction3 {
Up,
Down
}
console.log(Direction3.Up)
4.9. type
type 可以为任意类型创建别名,让代码更简洁,可读情更强,同时能更方便地进行类型复用和扩展
- 基本用法
// 类型别名使用 type 关键字定义,type 后跟类型名称,
type num = number
let price: num
price = 100
- 联合类型
// 联合类型是一种高级类型,它表示一个值可以是几种不同类型之一
type Status = number | string // | 叫管道符
type Gender = '男' | '女'
let sValue: Status = 100
let gValue: Gender = '男'
- 交叉类型
//交叉类型 允许将多个类型合并为一个类型,合并后的类型将拥有所有合并类型的成员,交叉类型通常用于对象类型
type Area = {
h:number
w:number
}
type Address = {
cell:number
room:string
}
type House = Area & Address
const house:House = {
h:100,
w:100,
cell:200,
room:"702"
}
4.10. 特殊情况
在函数定义时,限制函数返回值为 void,那么该函数的返回值就必须 undefined
function demo():void{
// return undefined
}
特殊情况:使用类型声明限制函数返回值为 void 时,TypeScript 并不会严格要求函数值
type DemoFunc = () => void
const f1: DemoFunc = () => { }
const f2: DemoFunc = () => {
return 100 //允许返回 非undefined
}
原因:是为了确保如下代码成立,Array.prototype.push 的返回一个数字,而 Array.prototype.forEach 方法期望其回调的返回类型是 void
const src = [1, 2, 3]
const dst = [0]
src.forEach((el) => dst.push(el))
4.11. class
类 class
Student 继承 Person
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
speak(){
console.log(`我叫:${this.name},今年${this.age}岁`)
}
}
let p = new Person('张三',19)
console.log(p)
p.speak()
class Student extends Person{
grade:string
constructor(name:string,age:number,grade:string){
super(name,age)
this.grade = grade
}
study(){
console.log("我在学习中")
}
// override 表示重写
override speak(): void {
console.log(`我叫:${this.name},今年${this.age}岁,上${this.grade}`)
}
}
4.12. 属性修饰符
修饰符 | 含义 | 具体规则 |
---|---|---|
public | 公开的 | 可以被:类内部、子类、类外部 访问 |
protected | 受保护的 | 可以被:类内部、子类 访问 |
private | 私有的 | 可以被: 类内部 访问 |
readonly | 只读属性 | 属性无法修改 |
public 修饰符
class Person {
public name: string
public age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
public speak(){
//类内部
console.log(`我叫:${this.name},今年${this.age}岁`)
}
}
class Student extends Person{
study(){
// 子类
console.log(`${this.name}我在学习中`)
}
}
//类外部
const p = new Person('张三',19)
console.log(p.age)
属性的简写形式
class Person {
public name: string
public age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
//简写
class Person2{
constructor(public name:string, public age:number){}
}
protected 修饰符
class Person {
constructor(protected name: string, protected age: number) {}
protected speak(){
//类内部
console.log(`我叫:${this.name},今年${this.age}岁`)
}
}
class Student extends Person{
study(){
// 子类
console.log(`${this.name}我在学习中`)
}
}
//类外部不能使用
const p = new Person('张三',19)
// console.log(p.age)
private 修饰符
class Person {
constructor(protected name: string,private age: number) {}
protected speak(){
//类内部可以使用
console.log(`我叫:${this.name},今年${this.age}岁`)
}
}
class Student extends Person{
study(){
// 子类不能使用
// console.log(`我在学习中,今年${this.age}岁`)
}
}
//类外部不能使用
const p = new Person('张三',19)
// console.log(p.age)
readonly 修饰符
class Person {
constructor(readonly name: string, private age: number) {}
}
//readonly 不能修改
const p = new Person('张三',19)
// p.name = "李四"
4.13. 抽象类
概述:抽象类相当于对子类的一种约束。
特点:
- 不能实例化,只能被继承
- 抽象方法必被子类重新实现
//对人进行约束(抽象类)
abstract class Person {
//构造属性
constructor(public name: string, public age: number) { }
//所有人必须要吃(抽象方法)
abstract eat():string
}
// 学生继承人类
class Student extends Person{
constructor(name:string,age:number,public book:string){
super(name,age)
}
override eat(){
return `${this.name}看完书:${this.book},就去吃饭`
}
}
const s = new Student('张三',18,'三国')
console.log(s.eat())
4.14. interface(接口)
interface 是一种定义结构的方式,主用作用是:类、对象、函数等规定的一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定格式,不能包含任何实现
定义类结构
// PersonInterface接口,用于限制Person类的格式
interface PersonInterface {
name: string
age: number
speak(n: number,m:number): void
}
// 定义一个类 Person,实现 PersonInterface 接口
class Person implements PersonInterface {
constructor(public name:string,public age:number){}
// 实现接口的 speak 方法,注意:实现speak时参数个数可以少于接口中的规定,但不能多
speak(n: number): void {
for(let i = 0; i< n;i++){
console.log(`我是${this.name},今年${this.age}岁了`)
}
}
}
const p1 = new Person('张三',14)
p1.speak(4)
定义对象结构
interface UserInterface {
name:string
readonly gender:string //只读属性
age?:number //可选属性
run(n:number):void
}
const user:UserInterface = {
name:'张三',
gender:'男',
run(n){
console.log(`跑了${n}米`)
}
}
定义函数结构
interface SumInterface {
(a:number,b:number):number
}
const sum:SumInterface = (x,y)=>{
return x + y
}
接口之间的继承
interface PersonInterface {
name:string
age:number
}
interface StudentInterface extends PersonInterface {
grade: string
}
接口自动合并(可重复定义)
interface PersonInterface {
name: string
age: number
}
interface PersonInterface {
gender: string
}
const p:PersonInterface = {
name:'tom',
age:18,
gender:'男'
}
总结:何时使用接口?
- 定义对象的格式:描述数据类型、API响应格式、配置对象 等,是开发中用的最多的场景
- 类的契约:规定一个类需要实现哪些属性和方法。
- 自动合并:一般用于扩展第三方库的类型,这种特性在大型项目中可能会用到
4.15. 一些相似概念的区别
interface 与 type 的区别
相同点:interface 与 type 都可以用于定义 对象结构,两者在许多场景中是可以互换的。
不同点:interface 更专注于对象和类的结构,支持继承、合并。type 可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并。
interface 与 抽象类 的区别
相同点:都用于定义一个类的格式
不同点:interface 只能描述结构,不能有任何实现代码,一个类可以实现多个接口,抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类
5. 泛型
泛型允许我们在事实上义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能停止同一段代码适用于多种类型,同时仍然保持类型的安全性。
泛型函数
// <T> 就是泛型,可以写别的,设置泛型后即可在函数中使用 T 来表示该类型
function logData<T>(data: T) {
console.log(data)
}
logData<number>(100)
logData<string>('hello')
泛型可以是多个
function logData<T,U>(data: T,data2: U) : T | U{
console.log(data,data2)
return Date.now() % 2 ? data1 : data2
}
logData<number,string>(100,'word')
logData<string,string>('hello','word')
泛型接口
interface PersonInterface<T> {
name:string
age:number
info:T
}
let p:PersonInterface<number> = {
name:'张三',
age:18,
info:14
}
6. 类型声明文件
类型声明文件是 TypeScript 中的一种特殊文件,通常以 .d.ts 作为扩展名,它的主要作用是 为现有的 JavaScript 代码提供错误,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行 类型检查和提示。
demo.js
export function add(a, b) {
return a + b;
}
export function mul(a, b) {
return a * b;
}
demo.d.ts
declare function add(a: number, b: number): number;
declare function mul(a: number, b: number): number;
export { add, mul };
index.ts
import { add, mul } from './demo.js'
console.log(add(1, 2))
console.log(mul(3, 4))
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>index</title>
</head>
<body>
<script type="module" src="./index.js"></script>
</body>
</html>
7. 装饰器
概述:
- 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
- 装饰器自 2015年在 ECMAScript-6 中被提出。
- 装饰器依然是实验特性,需要开发者手动调配置,来开启装饰器支持。
- 装饰器有 5 种:类装饰器、属性装饰器、方法装饰器、访问器装饰器、参数装饰器
备注:TypeScript5.0 中可以直接使用 类装饰器 ,但为了确保其他装饰器可用,现阶段使用时,仍建议使用 experimentalDecorators 配置来开启装饰器支持,在未来的版本中,官方会进一步调整装饰器的相关语法
7.1. 类装饰器
- 基本语法
类装饰器是一个应用在 类声明上 的函数,可以为类添加客同的功能,或添加额外的逻辑。
/*
Demo函数会在Person类定义时执行
参数说明:
target参数是被装饰的类,即:Person
*/
function Demo(target:Function){
console.log(target)
}
@Demo
class Person{}
- 应用举例
//需求:定义一个装饰器,实现Person 实例调用 toString 时返回 JSON.stringfy 的结果
//使用装饰器重写toString方法 + 封闭其原型对象
function CustomString(target:Function){
//向装饰器类的原型上添加自定义的 toString 方法
//prototype:原型
target.prototype.toString = function () {
return JSON.stringify(this)
}
//封闭其原型对象,禁止随意操作其原型对象
Object.seal(target.prototype)
}
//使用 CustomString 装饰器
@CustomString
class Person {
constructor(public name:string){}
speak(){
console.log("hello word")
}
}
let p = new Person("张三")
//测试输出
console.log(p.toString())
//禁止操作原型对象
interface Person {
x:any
}
// Person.prototype.x = 100
// console.log(p.x)
- 关于返回值
类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类。
类装饰器无返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换
function Demo(target: Function) {
//装饰器有返回值时,该返回值会被替换掉装饰的类
return class {
test() {
console.log(2000)
}
}
}
class Person {
test() {
console.log(100)
}
}
console.log(Person)
- 关于构造类型
在TypeScript中,Function类型所表示的范围十分广泛,包括:普通函数、简头函数、方法等。但并非 Function 类型的函数都可以被 new 关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript 中该如何声明一个构造类型呢?有以下两种方式:
//仅声明构造类型
/*
new 表示该类型可以用new操作符调用
...args 表示构造器可以接收任意数量的参数
any[] 表示构造器可以接收任意类型的参数
{} 表示返回类型是对象(非null、非undefined的对象)
*/
type Constructor = new (...args: any[]) => {};
function test(fn: Constructor) { }
class Person { }
test(Person)
//声明构造类型 + 指定静态属性
//定义一个静态类型,且包含一个静态属性 wife
type Constructor = {
new(...args: any[]): {};//构造签名
wife: string; //wife属性
}
function test(fn: Constructor) { }
class Person {
static wife = 'qwe'
}
test(Person)
- 替换被装饰的类
对于高级一些的装饰器,不仅仅是覆盖一个原型上的方法,还要有更多功能,例好添加新的方法和状态。
//设计一个LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法用于读取时间。
//User接口
interface User {
getTime():Date
log():void
}
//自定义类型Class
type Constructor = new (...args:any[]) => {}
//创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target:T){
return class extends target {
createdTime:Date;
constructor(...args:any[]){
super(...args)
this.createdTime = new Date()//记录对象创建时间
}
getTime() {
return `该对象创建时间为:${this.createdTime}`
}
}
}
@LogTime
class User{
name:string
age:number
constructor(name:string,age:number){
this.name = name
this.age = age
}
speak(){
console.log('你好')
}
}
const u = new User('张三',18)
console.log(u)
7.2. 装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的行为。
//需求:定义一个LogInfo类装饰器工厂,实现Person实例可以调用到introduce方法,且introduce中输出内容的次数,由LogInfo接收的参数决寂静
interface Person {
introduce:() => void
}
//定义一个装饰器工厂 LogInfo 它接收一个参数 n 返回一个类装饰器
function LogInfo(n:number){
//装饰器函数 target 是被装饰的类
return function(target:Function){
target.prototype.introduce = function(){
for(let i = 0;i<n;i++){
console.log(`我的名字:${this.name},我的年龄:${this.age}`)
}
}
}
}
@LogInfo(5)
class Person{
constructor(
public name:string,
public age:number
){}
speak(){
console.log('hello')
}
}
const p1 = new Person('张三',18)
p1.introduce()
7.3. 装饰器组合
装饰器可以组合使用,执行顺序为:先【由上到下】的执行所有的装饰器工厂,依次获取到装饰器,然后再【由下到上】执行所有装饰器。
//装饰器组合-执行顺序
//装饰器
function test1(target: Function) {
console.log('test1')
}
//装饰器工厂
function test2() {
console.log('test2工厂')
return function (target: Function) {
console.log('test2')
}
}
//装饰器工厂
function test3() {
console.log('test3工厂')
return function (target: Function) {
console.log('test3')
}
}
//装饰器
function test4(target: Function) {
console.log('test4')
}
@test1
@test2()
@test3()
@test4
class Person { }
//装饰器组合-应用
//处定义类型Class
type Constructor = new (...args: any[]) => {}
interface Person {
introduce(): void
getTime(): void
}
//使用装饰器重写toString方法 + 封闭原型对象
function customToString(target: Function) {
//向被装饰类的原型上添加处定义的toString 方法
target.prototype.toString = function () {
return JSON.stringify(this)
}
//封闭其原型对象,禁止随意操作其原型对象
Object.seal(target.prototype)
}
//创建一个装饰器,为类添加日志功能和创建时间
function LogTime<T extends Constructor>(target: T) {
return class extends target {
createdTime: Date;
constructor(...args: any[]) {
super(...args);
this.createdTime = new Date(); //记录对象创建时间
}
getTime() {
return `该对象创建时间为:${this.createdTime}`
}
}
}
//定义一个装饰器工厂 LogInfo,它接受一个参数 n, 返回一个类装饰器
function LogInfo(n: number) {
//装饰器函数, target 是被装饰的类
return function (target: Function) {
target.prototype.introduce = function () {
for (let i = 0; i < n; i++) {
console.log(`我的名字:${this.name},我的处龄:${this.age}`)
}
}
}
}
@customToString
@LogInfo(3)
@LogTime
class Person {
constructor(
public name: string,
public age: number
) { }
speak() {
console.log('hello')
}
}
const p = new Person('张三', 18)
console.log(p.toString())
p.introduce()
console.log(p.getTime())
7.4. 属性装饰器
- 基本语法
/*
参数说明:
targert: 对于静态属性来说值是类,对于实例属性来说值是类的实例对象
propertyKey: 属性名
*/
function Demo(target:object,propertyKey:string){
console.log(target,propertyKey)
}
class Person{
@Demo name:string
@Demo age:number
@Demo static school:string
constructor(name:string,age:number){
this.name = name
this.age = age
}
}
const p1 = new Person('张三',18)
- 关于属性遮蔽
//在构造器中的 this.age = age 试图在实例上赋值时,实际上是调用了原型上age属性的set方法
class Person {
name:string
age:number
constructor(name:string,age:number){
this.name = name
this.age = age
}
}
const p1 = new Person('张三',18)
//使用 defineProperty 给 Person原型添加age属性,并配置对应用的get与set
let value = 99
Object.defineProperty(Person.prototype,'age',{
get(){
return value
},
set(val){
value = val
}
})
const p2 = new Person('李四',19)
console.log(p2.age)//19
console.log(Person.prototype.age) //19
console.log(p2)
console.log(p1)
- 应用举例
//需求:定义一个 State 属性装饰器,来监视属性的修改。
//声明一个装饰器函数 State, 用于捕获数据的修改
function State(target:object, propertyKey:string){
//存储属性的内部值
let key = `__${propertyKey}`
//使用Object.defineProperty 替换类的原始属性
//重新定义属性,使其使用自定义的 getter 和 setter
Object.defineProperty(target,propertyKey,{
get(){
return this[key]
},
set(newValue:string){
console.log(`${propertyKey}的最新值为:${newVal}`)
this[key] = newVal
},
enumerable:true,
configurable:true,
})
}
class Person {
name:string
@State age:number
constructor(name:string,age:number){
this.name = name
this.age =age
}
}
const p1 = new Person('张三',18)
const p2 = new Person('李四',19)
p1.age = 30
p2.age = 40
console.log(p1.age)
console.log(p2.age)
7.5. 方法装饰器
- 基本语法
/*
参数说明:
target: 对于静态方法来说值是类,对于实例方法来说值是原型对象。
propertyKey: 方法名称。
descriptor: 方法的描述对象,其中value属性是被装饰的方法。
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person{
constructor(public name:string,public age:number){}
@Demo speak(){
console.log(`你好,我的名字是:${this.name},我的年龄:${this.age}`)
}
@Demo static isAudit(age:number){
return age>=18
}
}
const p1 = new Person('张三',18)
p1.speak()
- 应用举例
/*
需求:
1.定义一个Logger方法装饰器,用于在方法执行前和执行后,均追加一些额外逻辑。
2.定义一个Validate方法装饰器,用于验证数据
*/
function Logger(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
//保存原始方法
const original = descriptor.value;
//替换原始方法
descriptor.value = function (...args: any[]) {
console.log(`${propertyKey}开始执行......`)
const result = original.call(this, ...args)
console.log(`${propertyKey}执行完毕......`)
return result;
}
}
function Validate(maxValue: number) {
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
//保存原始方法
const original = descriptor.value;
//替换原始方法
descriptor.value = function (...args: any[]) {
//自定义的验证逻辑
if (args[0] > maxValue) {
throw new Error('年龄非法!')
}
//如果所有参数都符合逻辑,则调用原始方法
return original.apply(this, args);
}
}
}
class Person {
constructor(
public name: string,
public age: number
) { }
@Logger speak() {
console.log(`你好,我的名字:${this.name},我的年龄:${this.age}`)
}
@Validate(120) static isAudit(age: number) {
return age >= 18;
}
}
const p1 = new Person('张三', 18)
p1.speak()
console.log(Person.isAudit(100))
7.6. 访问器装饰器
- 基本语法
/*
参数说明:
target: 1.对于实例访问器来说值是【所属类的原型对象】
2.对于静态访问器来说值是【所属类】
propertyKey: 访问器的名称
descriptor: 描述对象
*/
function Demo(target:object,propertyKey:string,descriptor:PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person {
@Demo
get address(){
return '地址'
}
@Demo
static get contry(){
return '国家'
}
}
- 应用举例
/*
需求:对Weather类的temp属性的set访问器进行限制,设置的最低温度-50,最高温度 50
*/
function RangeValidate(min: number, max: number) {
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor) {
//保存原始的 setter 方法,以便在后续调用中使用
const originalSetter = descriptor.set;
//重写 setter 方法,加入范围验证逻辑
descriptor.set = function (value: number) {
//检查设置的值是否在指定的最小值和最大值之间
if (value < min || value > max) {
//如果值不在范围内,抛出错误
throw new Error(`${propertyKey}的值应该在 ${min} 到 ${max} 之间`)
}
//如果值在范围内,且原始 setter 方法存在,则调用原始 setter 方法
if (originalSetter) {
originalSetter.call(this, value)
}
}
}
}
class Weather {
private _temp: number;
constructor(_temp: number) {
this._temp = _temp
}
//设置温度范围在-50 到 50 之间
@RangeValidate(-50, 50)
set temp(value) {
this._temp = value
}
get temp() {
return this._temp
}
}
const w1 = new Weather(25)
console.log(w1)
w1.temp = 67
console.log(w1)
7.7. 参数装饰器
- 基本语法
/*
参数说明:
target:
1.如果修饰的是【实例方法】的参数,target 是类的【原型对象】
2.如果修饰的是【静态方法】的参数,target 是类。
propertyKey: 参数所在方法的名称
parameterIndex: 参数在函数参数列表中的索引,从0开始
*/
function Demo(target: object, propertyKey: string, parameterIndex: number) {
console.log(target)
console.log(propertyKey)
console.log(parameterIndex)
}
//类定义
class Person {
constructor(public name: string) { }
speak(@Demo message1: any, message2: any) {
console.log(`${this.name}想说:${message1},${message2}`)
}
}