×

Typescript---之装饰器

独孤求败 独孤求败 发表于2026-07-06 09:59:56 浏览22 评论0

抢沙发发表评论

个人理解
装饰器就是个扩展,它把那些类,方法,访问符号,属性 传递到装饰器里面,然后在装饰器里面在对他们改变

ECMAScript 的装饰器提案到现在还没有定案,所以我们直接看 TS 中的装饰器。同样在 TS 中,装饰器仍然是一项实验性特性,未来可能有所改变,所以如果你要使用装饰器,需要在 tsconfig.json 的编译配置中开启 experimentalDecorators,将它设为 true。

基础

装饰器是一种新的声明,它能够作用于类声明、方法、访问符、属性和参数上。使用@符号加一个名字来定义,如@decorat,这的 decorat 必须是一个函数或者求值后是一个函数,这个 decorat 命名不是写死的,是你自己定义的,这个函数在运行的时候被调用,被装饰的声明作为参数会自动传入。要注意装饰器要紧挨着要修饰的内容的前面,而且所有的装饰器不能用在声明文件(.d.ts)中,和任何外部上下文中(比如 declare,关于.d.ts 和 declare)。比如下面的这个函数,就可以作为装饰器使用:

function setProp(target){
// todo
}

@setProp
class Person{}

// 这里面的target 就是Person这个类

先定义一个函数,然后这个函数有一个参数,就是要装饰的目标,装饰的作用不同,这个 target 代表的东西也不同,下面我们具体讲的时候会讲。定义了这个函数之后,它就可以作为装饰器,使用@函数名的形式,写在要装饰的内容前面。

装饰器工厂(传值)

装饰器工厂也是一个函数,它的返回值是一个函数,返回的函数作为装饰器的调用函数。如果使用装饰器工厂,那么在使用的时候,就要加上函数调用,如下:

function setProp(params){
returnfunction(target){
// todo...
};
}

@setProp()
class Person{}
装饰器组合

装饰器可以组合 也就是同一个目标可以有多个装饰器

@setProp()
@setProp()
class Person{}
装饰器执行顺序

装饰器工厂从上到下依次执行,但是只是用于返回函数 但不调用函数


装饰器函数由内到外执行,也就是执行工厂函数的返回函数,越靠近目标的装饰器,里面的函数越先执行。


举例


function setName(){
  console.log("get setName");
return function(target){
  console.log("setName");
};
}
function setAge(){
  console.log("get setAge");
return function(target){
  console.log("setAge");
};
}
@setName()
@setAge()
classTest{}
// 打印出来的内容如下:
/**
 'get setName'
 'get setAge'
 'setAge'
 'setName'
*/
装饰器求值

类的定义中不同声明上的装饰器将按以下规定的顺序引用:

参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员;


参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员;


参数装饰器应用到构造函数;


类装饰器应用到类。


类装饰器

类装饰器在类声明之前声明,要记着装饰器要紧挨着要修饰的内容,类装饰器应用于类的声明。


类装饰器表达式会在运行时当做函数被调用,它由唯一一个参数,就是装饰的这个类。


let sign =null;
function setName(name:string){
return function(target:Function){
    sign = target;
    target.name = name;
};
}
@setName("lison")// Info
classInfo{
constructor(){}
}
console.log(Info.name);// lison
console.log(sign === Info);// true
console.log(sign === Info.prototype.constructor);// true

可以看到,我们在装饰器里打印出类的 name 属性值,也就是类的名字,
我们没有使用 Info 创建实例,控制台也打印了"Info",因为装饰器作用与装饰的目标声明时
而且我们将装饰器里获取的参数 target 赋值给 sign,
最后判断 sign 和定义的类 Info 是不是相等,如果相等说明它们是同一个对象,结果是 true。
而且类 Info 的原型对象的 constructor 属性指向的其实就是 Info 本身。

这样通过装饰器,我们就可以修改类的原型对象和构造函数

举例

function logClass(params:any){
console.log(params);
// params 就是当前类
  params.prototype.apiUrl ="动态扩展的属性";
  params.prototype.run=function(){
   console.log("我是一个run方法");
};
}

@logClass
class HttpClient{
constructor(){}
getData(){}
}
var http:any=new HttpClient();
console.log(http.apiUrl);
http.run();
装饰器工厂(可传参)

target 就是类 params 就是参数

function logClass(params:string){
return function(target:any){
      console.log(target);
      console.log(params);
    target.prototype.apiUrl = params;
};
}

@logClass("http://www.loaderman.com/api")
class HttpClient{
constructor(){}
getData(){}
}

var http:any=new HttpClient();
console.log(http.apiUrl);
类装饰器重载构造函数

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

function logClass(target:any){
  console.log(target);
return classextends target {
    apiUrl:any="我是修改后的数据";
      getData(){
      this.apiUrl =this.apiUrl +"----";
      console.log(this.apiUrl);
}
};
}

@logClass
class HttpClient{
public apiUrl:string|undefined;
constructor(){
this.apiUrl ="我是构造函数里面的apiUrl";
}
getData(){
console.log(this.apiUrl);
}
}

var http =new HttpClient();
http.getData();

// 我是修改后的数据----
属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列 2 个参数: 1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。 2、成员的名字。

//类装饰器
function logClass(params:string){
  return function(target:any){
     console.log(target);
     console.log(params);
};
}

//属性装饰器

function logProperty(params:any){
  return function(target:any, attr:any){
      console.log(target);
      console.log(attr);
    target[attr]= params;
};
}
@logClass("xxxx")
class HttpClient{
  @logProperty("http://loaderman.com")
  public url:any|undefined;
  @logProperty("http://loaderman333.com")
  public urldata:any|undefined;
  constructor(){}
  getData(){
     console.log(this.url);
     console.log(this.urldata);
  }
}
var http =new HttpClient();
http.getData();
方法装饰器
第一种(常用)

它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。

方法装饰会在运行时传入下列 3 个参数:

1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。

2、成员的名字。

3、成员的属性描述符。

注意:在 vscode 编辑时有时会报作为表达式调用时,无法解析方法修饰器的签名。错误,此时需要在 tsconfig.json 中增加 target 配置项:

{
"compilerOptions":{
"target":"es6",
"experimentalDecorators":true,
}
}
代码如下

function  get(params:any){
console.log(params);
  return  function(target:any, methodName:any, desc:any){
            console.log(target);
            console.log(methodName);
            console.log(desc);
            target.apiUrl ="xxxx";
          target.run=function(){
                    console.log("run");
     };
  };
}
class Httpclient{
  public url:any|undefined;
  constructor(){}
  @get("http://www.itying.com")
  getData(){
     console.log(this.url);
    }
}

var http:anynew Httpclient();
console.log(http.apiUrl);
http.run();
方法装饰器二

在方法装饰器里面修改当前方法,把当前方法里面的参数修改成字符串

function get(params:any){
  console.log(params);
   return  function(target:any, methodName:any, desc:any){
              console.log(target);
              console.log(methodName);
              console.log(desc.value);

//修改装饰器的方法﹑把装饰器方法里面传入的所有参数改为string类型
//1、保存当前的方法
               var oMethod = desc.value;
             desc.value=function(...args:any[]){
            args = args.map((value)=>{
               return String(value);
});

console.log(args);
oMethod.apply(this, args);
};
};
}
class Httpclient{
  public url:any|undefined;
  constructor(){}
  @get("http://www.itying.com")
  getData(...args:any[]){
  console.log("我是getData里面的方法", args);
}
}

var http =new Httpclient();
http.getData(123,"xxx");
方法装饰器三

参数装饰器表达式会在运行时当作函数被调用,传入下列 3 个参数:

1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。


2、方法的名字。


3、参数在函数参数列表中的索引。


function logParams(params:any){
  console.log(params);
return function(target:any, methodsName:any, paramsIndex:any){
  console.log(target);
  console.log(methodsName);
  console.log(paramsIndex);
};
}

class Httpclient{
  public url:any|undefined;
  constructor(){}
  getData(@logParams("uuid") uuid:any){
  console.log("我是getData里面的方法");
  console.log(uuid);
  }
}

var http =new Httpclient();
http.getData(123456);
装饰器加载顺序
function ClassDecorator(){
  return function(target){
    console.log("I am class decorator");
   };
}
function MethodDecorator(){
return function(target, methodName:string, descriptor: PropertyDescriptor){
    console.log("I am method decorator");
   };
}
function Param1Decorator(){
return function(target, methodName:string, paramIndex:number){
   console.log("I am parameter1 decorator");
  };
}
function Param2Decorator(){
  return function(target, methodName:string, paramIndex:number){
     console.log("I am parameter2 decorator");
  };
}
function  PropertyDecorator(){
  return  function(target, propertyName:string){
     console.log("I am property decorator");
  };
}

@ClassDecorator()
class Hello{
   @PropertyDecorator()
  greeting:string;
   @MethodDecorator()
   greet(@Param1Decorator() p1:string,@Param2Decorator() p2:string){}
}
输出结果

I am property decorator
I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am classdecorator
总结

从上述例子得出如下结论:

1、属性装饰器 》方法装饰器》方法参数 装饰器》类装饰器

2、如果有多个同样的装饰器时:从最后一个装饰器依次向前执行

3、方法装饰器和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行。上述例子中属性和方法调换位置,输出如下结果:

I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am property decorator
I am classdecorator


群贤毕至

访客