记一次在项目中使用类装饰器

552 阅读5分钟

之前读阮一峰老师的《ECMAScript6入门》的看到了Decorator,翻译过来就是装饰器,当时只看了个大概,记住有这个东西。最近在项目上有个需求我突然想到用这个装饰器来实现这个功能挺优雅的,就记录一下使用的一点心得。

需求场景

公司有一个活动(移动端),需要注册用户,用户分两种,注册完成之后可以上传文章。但是这些功能在另外一个项目都具备了,刚开始的时候,我们主管是说的把功能复制过来,但是作为一个资深的搬砖工程师,怎么可能去复制,这两个功能原来的代码讲起来少说也有几千行,复制是不可能复制的,还是表单这种功能,里面牵扯到的逻辑实在太多了(懂的都懂)。

不复制但还是要解决项目的需求啊,就想到了从活动页面跳转到那个已经做好的项目地址中去,在url上面带上参数,在那个页面处理好之后再跳转回来,这样就会去修改原来代码的逻辑,怎么得也要加个 if 判断吧,我们并不希望修改原来的逻辑,这个本就不属于那个项目的内容。在思考怎么解决优雅的时候就想到了之前看到的Decorator 只需要在原来提交函数上装饰上一个方法就能控制我们需要处理的内容。(这里说明一下项目是vue class component写法)

Decorator

装饰器是一个函数,语法就是 @ + 函数名。可以放在类和类方法的定义前面。使用装饰器不仅可以增加代码的可读性,清晰表达意图。而且可以在不修改原有代码的结构外,增强或修改类的功能。类似java语言的注解。

类的装饰

@testClass
class TestDecorator {
    // ...
}

function testClass(target) {
    target.isTestClass = true;
}

上面的这段伪代码中,@testClass就是一个装饰器。它修改了TestDecorator这个类的行为,为它加上了静态属性isTestClasstestClass函数的参数target就是TestDecorator类。也就是说装饰器就是一个对类进行处理的函数。 当然你觉得一个参数不够用,可以在装饰器外面封装一层函数。

functon testClass(isTestClass) {
    return function(target) {
        target.isTestClass = isTestClass;
    }
}
@testClass(flase)
class TestClass {}
TestClass.isTestClass // false

上面这个例子中,装饰器testClass可以接受参数,这就等于可以修改装饰器的行为。

需要注意的是,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。

方法的修饰

修饰器不仅可以装饰类,还可以装饰类的属性。

class Person {
    @readonly
    name() { return `${this.first} ${this.last}` }
}

上面的代码中,装饰器readonly用来修饰“类”的name方法。 装饰器函数readonly一共可以三个参数。

function readonly(target, name, decorator) {
    decorator.writable = false;
    return decorator;
}

装饰器第一个参数是类的原型对象,上例是Person.prototype,装饰器的本意是要装饰类的实例,但是这个时候实例还没生成,所以只能去装饰原型;第二个参数是所要装饰的属性名;第三个参数是该属性的描述对象。

现在我们实现一个 @log装饰器,可以起到输出日志的作用。

class Math {
    @log
    add(a, b) {
        return a + b;
    }
}

function log(target, name, decorator) {
    var fn = decorator.value;
    decorator.valur = function() {
        console.log(`Calling ${name} with`, arguments);
        return fn.apply(this, arguments);
    }
    
    return decorator;
}

@log装饰器的作用就是在执行原始的操作之前,执行一次console.log,从而实现输出日志的目的。

需要注意的是,一个方法可以有多个装饰器,会像剥洋葱一样,先从外到内进入,然后之内向外执行,所以当有多个装饰器时一定要注意顺序。

function dec(id){
  console.log('evaluated', id);
  return (target, property, decorator) => console.log('executed', id);
}

class Example {
    @dec(1)
    @dec(2)
    method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1

core-decorators.js

core-decorators.js是一个第三方模块,提供几个常用的装饰器,通过它可以让我们更好的理解装饰器。

@autobind

这个装饰器使得方法中的this对象,绑定原始对象。

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
  	return this;
  }
}

let person = new Person();
let { getPerson } = person;

getPerson() === person;
// true

@readonly

readonly装饰器使得属性或方法不可写。

import { readonly } from 'core-decorators';

class Meal {
  @readonly
  entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]

@override

@override装饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。java语言中也有一个类似的注解。

import { readonly } from 'core-decorators';

class Meal {
  @readonly
  entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]

@nonenumerable

@nonenumerable装饰器是装饰属性的,表示该类属性不能枚举。

import { nonenumerable } from 'core-decorators';

class Meal {
  entree = 'steak';

  @nonenumerable
  cost = 20.99;
}

var dinner = new Meal();
for (var key in dinner) {
  key;
  // "entree" only, not "cost"
}

Object.keys(dinner);
// ["entree"]

以上的例子来之官方,更多内容可以查看官方文档

我们项目使用到的装饰器

前面提到我们项目是使用vue-class-components加上typescript的写法,所以也引用VuePropertyDecorator,该模块中提供了一些修饰属性的装饰器,在这里就不过多赘述了,有兴趣的同学可以查看官方文档

结尾

参考文章:

ECMAScript6 入门

core-decorator文档

VuePropertyDecorator

以上就是本篇的全部内容了,如果大家喜欢可以点个赞,这真的对我很重要!