特殊的函数
,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁类声明上的函数
,可以为类添加额外的功能,或添加额外的逻辑function Demo(target: Function) {
console.log(target);
}
@Demo
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
function CustomString(target: Function) {
target.prototype.toString = function () {
return JSON.stringify(this);
};
// 密封对象,禁止添加、删除或重新配置属性
// Object.seal(target.prototype);
}
@CustomString
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const person = new Person("张三", 18);
console.log(person);
console.log(person.toString());
console.log(JSON.stringify(person));
// interface Person {
// x: number;
// }
// Person.prototype.x = 1;
// console.log(Person.prototype.x);
有
返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类无
返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换function Demo(target: Function) {
return class{
test(){
console.log(200);
}
}
}
@Demo
class Person {
test(){
console.log(100);
}
}
console.log(Person);
/**
* 构造函数类型
* new 表示:该类型是可以用new操作符调用的
* ...args 表示:该类型可以接受任意数量的参数
* any[] 表示:该类型可以接受任意类型的参数
* {} 表示:返回类型是对象(非 null,非 undefined )
*/
type Constructor = new (...args: any[]) => {};
function test(target: Constructor) {}
class Person {}
test(Person);
type Constructor = {
new (...args: any[]): {};
wife:string //静态属性
};
function test(target: Constructor) {}
class Person {
static wife: "张三";
}
test(Person);
type Constructor = new (...args: any[]) => {}
interface Person {
getCreatedTime(): void;
}
function LogTime<T extends Constructor>(target: T) {
return class extends target {
createdTime: Date;
constructor(...args: any[]) {
super(...args);
this.createdTime = new Date();
}
getCreatedTime() {
return `this.createdTime: ${this.createdTime}`;
}
}
}
@LogTime
class Person {
constructor(public name: string, public age: number) { }
speak() {
console.log("说话");
}
}
const person = new Person("张三", 18);
console.log(person);
console.log(person.getCreatedTime());
装饰器工厂是一个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活的控制装饰器的行为
需求:定义一个LogInfo
类装饰器工厂,实现Person
实例可以调用到introduce
方法,且introduce
中输出内容的次数,由LogInfo
接收的参数决定。
interface Person {
name: string;
age: number;
introduce: () => void;
}
function LogInfo(n:number) {
return function (target: Function) {
target.prototype.introduce = function () {
for (let i = 0; i < n; i++) {
console.log("我的名字是" + this.name + ",我今年" + this.age + "岁");
}
}
}
}
@LogInfo(2)
class Person {
constructor(public name: string, public age: number) { }
speak() {
console.log("说话");
}
}
const person = new Person("张三", 20);
person.introduce();
装饰器可以组合使用,执行顺序为:先【由上到下】
的执行所有的装饰器工厂,依次获取到装饰器,然后再【由下到上】
执行所有的装饰器。
// 装饰器
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 {}
/**
* test2工厂
* test3工厂
* test4
* test3
* test2
* test1
*/
interface Person {
introduce: () => void
getCreatedTime: () => void
}
// 使用装饰器重写toString方法+封闭其原型对象
function CustomToString(target: Function) {
target.prototype.toString = function() {
return JSON.stringify(this)
}
Object.seal(target.prototype)
}
// 定义一个装饰器工厂LogInfo,它接受一个参数n,并返回一个类装饰器
function LogInfo(n: number) {
return function(target: Function) {
target.prototype.introduce = function() {
for (let i = 0; i < n; i++) {
console.log(`我叫${this.name},我今年${this.age}岁`)
}
}
}
}
// 创建一个装饰器,为类添加日志功能和创建时间
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()
}
getCreatedTime() {
return `创建时间: ${this.createdTime}`
}
}
}
@CustomToString
@LogInfo(3)
@LogTime
class Person {
constructor(public name: string, public age: number) {}
speak(){
console.log(`你好啊`)
}
}
const p = new Person('张三', 18)
p.speak()
console.log(p.toString())
p.introduce()
console.log(p.getCreatedTime())
注意报错:作为表达式调用时,无法解析类修饰器的签名
//tsconfig.json文件
{
"compilerOptions": {
//...
"experimentalDecorators": true
}
}
基本语法
/*
* 参数说明
* target:对于静态属性来说值是类,对于实例属性来说值是类的原型对象
* 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
}
}
属性遮蔽
如下代码中,当构造器中的this.age=age试图在实例上赋值时,实际上是调用了原型上age属性的set方法
class Person {
name: string
age: number
static school: string
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
let value = 10
Object.defineProperty(Person.prototype, 'age', {
get() {
return value
},
set(val) {
value = val
}
})
const p = new Person('张三', 18)
console.log(p)
应用举例
需求:定义一个State属性装饰器,来监视属性的修改。
function state(target: object, propertyKey: string) {
let key =`_${propertyKey}`
Object.defineProperty(target, propertyKey, {
get() {
return this[key]
},
set(val) {
console.log(`${propertyKey}被修改为:${val}`)
this[key] = val
},
enumerable: true,
configurable: true
})
}
class Person {
name: string
@state age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
const p = new Person('张三', 18)
/**
* 参数说明:
* target:对于静态方法来说值是类,对于实例方法来说值是类的原型对象
* propertyKey: 方法的名称
* descriptor: 方法的描述对象,其中value属性是被装饰的方法
* writable属性表示是否可写,enumerable属性表示是否可枚举,configurable属性表示是否可配置
*/
function Demo(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target, propertyKey, descriptor)
}
class Person {
constructor(public name: string, public age: number) {
}
@Demo
speak() {
console.log(`你好啊`)
}
static isAdult(age: number) {
return age >= 18
}
}
const p = new Person('张三', 18)
p.speak()
console.log(Person.isAdult(18))
function Logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function (...args: any[]) {
console.log(`日志记录:${propertyKey}被调用,参数为:${JSON.stringify(args)}`)
const result = originalMethod.call(this, ...args)
console.log(`日志记录:${propertyKey}返回值为:${JSON.stringify(result)}`)
return result
}
}
function Validate(maxValue: number) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function (...args: any[]) {
if (args[0] > maxValue) {
throw new Error(`年龄非法`)
}
return originalMethod.apply(this, args)
}
}
}
class Person {
constructor(public name: string, public age: number) {
}
@Logger
speak() {
console.log(`你好啊,${this.name},${this.age}岁`)
}
@Validate(14)
static isAdult(age: number) {
return age >= 18
}
}
const p = new Person('张三', 18)
p.speak()
console.log(Person.isAdult(18))
/**
* 参数说明
* target: 对于实例访问器来说值是【所属类的原型对象】,对于静态访问器来说值是【所属类】
* propertyKey: 访问器的名称
* descriptor: 描述对象
*/
function Demo(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target)
console.log(propertyKey)
console.log(descriptor)
}
class Person {
@Demo
get address() {
return '北京'
}
@Demo
static get country() {
return '张三'
}
}
class Weather {
private _temperature: number;
constructor(temperature: number) {
this._temperature = temperature;
}
@RangeValidator(-50, 50)
set temperature(value: number) {
this._temperature = value;
}
get temperature() {
return this._temperature;
}
}
function RangeValidator(min: number, max: number) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原方法
const originalMethod = descriptor.set;
// 重写set方法
descriptor.set = function (value: number) {
if (value < min || value > max) {
throw new Error(`温度非法`)
}
originalMethod?.call(this, value)
}
}
}
const weather = new Weather(20)
weather.temperature = 90
console.log(weather.temperature)
基本语法
/**
* 参数说明
* target: 如果修饰的是实例方法的参数,则target是类的原型对象,如果修饰的是静态方法的参数,则target是类
* propertyKey: 访问器的名称
* parameterIndex: 参数在函数参数列表中的索引,从0开始
*/
function Demo(target: any, propertyKey: string, parameterIndex: number) {
console.log(target)
console.log(propertyKey)
console.log(parameterIndex)
}
class Person {
constructor(public name: string, public age: number) {
}
speak(@Demo message: string, msg: string) {
console.log(`${this.name}说:你好,${message},${msg}`)
}
}
const person = new Person('张三', 18)
person.speak('你好', '世界')