JS中装饰器到底是什么?

作者: BirdNiao | 来源:发表于2018-07-15 22:23 被阅读59次

    因主要的技术栈是Angular,对于Angular采用的装饰器特别认可,是一种优雅的拦截JS的方式。

    TC39的装饰器提案其实共3个:class类和类属性装饰器、function函数装饰器、parameter方法参数装饰器,后2个仍处于Stage 0中,因此本文只针对class装饰器

    目前Decorator仍处于Stage 2的阶段,不知道能否在ES2019(ES10)中推出,但一个提案只要能进入Stage 2,就基本会包括在以后的正式标准里面。

    有N多文章写道Decorator是ES2016(ES7)推出的,不知道这是从哪里流传出来的,ES2016最终特性根本就没有Decorator,可能的原因:Decorator只是有望在ES2016推出的,实际上并没有。

    image

    以Angular中的一个组件为例,来说明装饰器的主要用法和装饰器到底是什么:

    @Component({
      selector: 'app-transfer-common',
      templateUrl: './transfer-common.component.html',
      styleUrls: ['./transfer-common.component.scss']
    })
    @AutoUnsubscribe()
    export class TransferCommonComponent implements OnInit {
      whichRouter: String;
      btnShowStatus: ShowOrHideBtn;
      queryParamsSubscribe: any;
      userInfo: any = this.commonUserService.getUserInfo(); 
      i18ns_common;
      constructor(
        private router: Router,
        private route: ActivatedRoute,
        private returnVariousBtn: ReturnVariousRouterParamsService,
        private commonUserService: CommonUserService,
        private httpService: HttpService,
        private translate: TranslateService,
      ) { }
    
      ngOnInit() {
      }
    
      // 跳转到新增
      @getProperty
      gotoAdd() {
      }
    
      /* ngOnDestroy() {
        this.queryParamsSubscribe.unsubscribe();
    
      } */
    }
    

    一.类装饰器

    AutoUnsubscribe是一个自动取消订阅的装饰器,可传入参数指定某个可订阅对象不自动取消订阅。如不需要指定,可取消外层高阶函数

    export function AutoUnsubscribe(params: string) {
      return function (constructor) {
        console.log(constructor);
        console.log(constructor.prototype);
        const originNgDestory = constructor.prototype.ngOnDestroy;
        constructor.prototype.ngOnDestroy = function () {
          console.log(this);
          console.log(this.constructor);
          console.log(constructor);
          for (const property of Object.values(this)) {
            if (property && (typeof property.unsubscribe === 'function')) {
              console.log(property);
            }
          }
          originNgDestory && typeof originNgDestory === 'function' && originNgDestory.apply(this);
        };
      };
    }
    

    1.类装饰器仅仅是一个接受一个参数的、被装饰的类的构造函数,常用于修改、添加类的原型方法

    2.类装饰器传入的constructor就是类的constructor(特指constructor(){}),如下图红色部分.类装饰器作用于类的 constructor,并且观察、修改或者替换一个类的定义。

    3.this是整个class,如图天蓝色外框
    4.特殊的是:类的属性方法在类的proto中,其他的是类的直接子集,如图靑蓝色部分

    4.参数constructor === this.constructor

    5.执行订阅的时候,订阅的是一个Subscribe对象,property.unsubscribe是从原型链上获取的unsubscribe,如下图绿色部分


    image

    二.类属性(方法)装饰器

    // 下一个事件队列中执行的装饰器
    export const timeoutDecorator = function (milliseconds: number = 0) {
      return function (target, key, descriptor: any) {
        console.log(target);  // 即整个对象
        console.log(key);     // 装饰器修饰的某个key
        console.log(descriptor);
        const originalMethod = descriptor.value;  // value即这个key对应的方法
        
        /** 当执行gotoAdd方法时,即先执行以下函数 */
        descriptor.value = function (...args) {
          console.log(args);
          setTimeout(() => {
            originalMethod.apply(this, args);       // gotoAdd方法调用
          }, milliseconds);
        };
        console.log(descriptor);
        return descriptor;
      };
    };
    
    export const getProperty = function (target, name, descriptor) {
      console.log(target);
      console.log(name);
      console.log(descriptor);
      const originalMethod = descriptor.value;
      descriptor.value = function (...args) {
        console.log(args);
        originalMethod.apply(this, args);
      };
      console.log(descriptor);
      return descriptor;
    };
    
    
    image

    1.类方法装饰器是一个函数,函数参数就是Object.defineProperty中的三个参数即:target(目标对象)、key(调用的属性名)、descriptor(调用的属性的描述,包括configurable、enumerable、writable、value)

    2.类方法装饰器的作用是把类中的方法放入装饰器中执行,个人理解类似于管道或者拦截器

    3.类的普通属性(不是方法的)也可以添加装饰器,比如Angular中的@ViewChild,@ViewContent

    三.修饰器本质就是编译时执行的函数

    注意,不管哪种修饰器,对类的行为的改变,是代码编译时(初始化时)发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。

    表现上:添加上装饰器后,装饰器中descriptor.value = function(...args) ...... 函数外的内容会先执行.然后在每次触发类中的修饰的方法是会才会调用descriptor.value = function(...args) ...... 中的内容,args是指触发此方法时的实际参数.再通过apply方法,调用需要修饰的类方法,注意不一定是指向this,也有可能是target,具体看情况

    四.特别注意:this

    箭头函数中的this和普通函数的this是不同的,注意函数体内是否有this,有的话就不要用箭头函数了,详情请搜索箭头函数的this


    划重点

    1.类装饰器和类方法装饰器是不同的,但本质都是一个函数
    2.类装饰器常用于修改、添加类的原型方法,类方法装饰器用于拦截类方法,需要掌握具体写法
    3.注意箭头函数和this的搭配使用问题

    参考资料:

    ECMAScript 6 入门

    ECMAScript proposals

    decorator

    相关文章

      网友评论

        本文标题:JS中装饰器到底是什么?

        本文链接:https://www.haomeiwen.com/subject/skjmpftx.html