JavaScript Decorators

javascript decorator

现在什么 AOP 编程在前端领域越来越被大家追捧,所以我也来探究一下如何在javascript中进行AOP编程。 装饰器无疑是对AOP最有力的设计,在es5 时代,可以通过 Object.defineProperty 来对对象属性/方法 进行访问修饰,但用起来需要写一堆东西。现在decorator已经在ES7的提案中了,借助Babel等转码工具,我们现在也能在javascript中使用装饰器语法了!

什么是Decorator

decorator 也叫装饰器(装潢器)。它可以在不侵入到原有代码内部的情况下而通过标注的方式修改类代码行为,装饰器对代码行为的改变是在编译阶段完成的,而不是在执行阶段。虽然Decorator还处在ES7草案阶段,但是我们可以通过Babel来转换es7代码,所以大家还是可以愉快的使用decorator。
在ES7提案中,Decorator的描述如下:

  • an expression
  • that evaluates to a function
  • that takes the target, name, and decorator descriptor as arguments
  • and optionally returns a decorator descriptor to install on the target object.

出自 https://github.com/wycats/javascript-decorators

在代码层面,Decorator其实就是一个函数。

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

let o = {
  @readonly  // 标识为只读属性
  name: 'liuyan'
}

// 赋值失败并报错
o.name = 'liuzheng'; // Cannot assign to read only property 'name' of object '#<Object>'

上面的代码实现了一个简单的装饰器用来使对象属性只读。函数readonly 规定了装饰器描述符的行为。不难看出,这和ES5中的 Object.defineProperty 方法很类似,使用es5代码一样能够实现相同的功能,其实使用Babel转码最终也就是转换成了Object.defineProperty 的实现形式,只是使用 @readonly 这种语法更能直观的描述出来, 对比Java中的注解、 Python中的装饰器其实都使用类似的语法。

Decorator用法

给属性添加Decorator
和前面的例子一样,有时候需要在JS中实现类静态成员,这个时候就可以使用Decorator来修饰了,代码如下:

// 示例
class Person {
  @readonly
  static MIN_AGE = 0;
}

这样,当不小心重新为 Person.MIN_AGE 赋值的时候,就会抛出错误。

给方法添加Decorator
也可以对方法进行装饰。比如现在需要实现一个功能: 设计一个装饰器,它能够统计出一个异步方法(这里只用Promise)的耗时。还是以Person类为例,给Person增加一个request方法,统计request执行耗时,代码实现非常简单:

class Person {
  static MIN_AGE = 0;
  constructor(name) {
    this.name = name
  }
 
  @duration
  request() {
    return new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve({status: 0})
       }, 3000);
    })
  }
}

// 装饰器
function duration(target, key, desc) {
  const { value } = desc;
  let _time = Date.now();
  desc.value = function(...args) {
    let res = value.apply(this, args);
    if (res && typeof res.then === 'function') {
      res.then(() => {
        console.log(`${key}() ==> 耗时:${Date.now() - _time}ms`);
      }, () => {
        console.log(`${key}() ==> 耗时:${Date.now() - _time}ms`);
      })
    } else {
      console.log(`${key}() ==> 耗时:${Date.now() - _time}ms`);
    }
    return res;
  }
  // 需要把描述对象返回
  return desc;
}

// 开始
var p = new Person('liuyan');
p.request(); // 输出:  request() ==> 耗时:3002ms

作用于class
也可以在为class 应用装饰器,现在我要通过装饰器给Person类增加一个静态属性IS_PERSON; (当然,这没什么卵用...)

// 增加静态属性IS_PERSON
@isPerson
class Person {
  ...
}

function isPerson(target) {
  target.IS_PERSON = true;
}

console.log(Person.IS_PERSON); // true

也可以作用于class的实例属性

class Person {
  ...
}

function sayHi(target) {
  const {sayHi} = target.prototype;
  target.prototype.sayHi = function(...args) {
    if (typeof sayHi === 'function') {
      var res = sayHi.apply(this, args);
    }
    console.log(`Hi, I\'m ${this.name}`);
    return res;
  };
}

var p = new Person('liuyan');
p.sayHi(); // Hi, I'm liuyan

decorator作用于类最常见的用法就是mixins了,mixin 也就是允许我们为组件(类) 附加额外的功能,用过react的童鞋应该对mixin不陌生,不过使用mixin扩展新功能这种用法已经不被推荐了。
decorator已经在各知名框架中开始大面积使用,比如Angular2(ng2), 虽然ng2使用TypeScript 来构建的,但是装饰器这种语法实现也是大同小异的。下图是从angular js官网截取的示例代码:

Angular2中,大量使用了decorator

实际使用场景(Logger)

一个东西被吹得再好,如果没有使用场景那也是白搭。
在实际业务中,很多时候把装饰器用在日志工具上面,因为日志这种东西和业务几乎是完全分离的,试想一下,如果业务代码里面参杂了各种各样的日志信息...., 对于阅读代码逻辑以及维护来说都是灾难性的,这个时候我们的decorator就能派上用场了。
假设需要实现一个对定时任务的监控logger, 需要监控何时开始、结束,以及任务运行耗时的信息。代码如下

class ScheduleJob {
  constructor(name) {
    this.name = name;
  }
  @log('info', '开始')
  start() {
    setTimeout(() => {
      this.stop();
    }, 2000);
  }
  
  @log('info', '结束')
  stop() {}
}

var job = new ScheduleJob('liuyan');
job.start();

//输出: 
//Thu Jan 05 2017 18:34:09 GMT+0800 (CST) - info - 开始 
//Thu Jan 05 2017 18:34:09 GMT+0800 (CST) - info - 开始....time: 1483612449481
//Thu Jan 05 2017 18:34:12 GMT+0800 (CST) - info - 结束 
//Thu Jan 05 2017 18:34:12 GMT+0800 (CST) - info - 结束....time: 1483612452258,耗时:2777ms 

function log(t = 'info', msg = '') {
  return function(target, name, desc) {
    
    const {value} = desc;
    
    desc.value = function(...args) {
      console.log(`${new Date()} - ${t} - ${msg} `)
      let res = value.apply(this, args);
      if(name === 'start') {
        this[`startTime`] = Date.now();
        console.log(`${new Date()} - ${t} - 开始....time: ${this[`startTime`]}`)
      }
      if( name === 'stop' ) {
        this[`endTime`] = Date.now();
        console.log(`${new Date()} - ${t} - 结束....time: ${this[`endTime`]},耗时:${this[`endTime`] - this[`startTime`]}ms`)
      }
    }
  }
}

上面的是一个很简单的需求,我们没有修改原有类的任何代码就实现了日志监控。其实这种实现在编程器思想里面叫做 AOP,中文名也叫面向切面编程,java里面用得非常之多。

网上有牛人写了一些常用的decorators core-decorators,源码比较简单,可以学习学习。

参考资料

decorator描述: https://github.com/wycats/javascript-decorators
core-decorators.js https://github.com/jayphelps/core-decorators.js
decorators 文档 http://tc39.github.io/proposal-decorators/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容