设为首页 加入收藏

TOP

Javascript装饰器的妙用(一)
2018-07-13 06:06:56 】 浏览:318
Tags:Javascript 装饰 妙用

最近新开了一个Node项目,采用TypeScript来开发,在数据库及路由管理方面用了不少的装饰器,发觉这的确是一个好东西。
装饰器是一个还处于草案中的特性,目前木有直接支持该语法的环境,但是可以通过 babel 之类的进行转换为旧语法来实现效果,所以在TypeScript中,可以放心的使用@Decorator。


什么是装饰器


装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为。
通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。
个人认为装饰器是一种解决方案,而并非是狭义的@Decorator,后者仅仅是一个语法糖罢了。


装饰器在身边的例子随处可见,一个简单的例子,水龙头上边的起泡器就是一个装饰器,在装上以后就会把空气混入水流中,掺杂很多泡泡在水里。
但是起泡器安装与否对水龙头本身并没有什么影响,即使拆掉起泡器,也会照样工作,水龙头的作用在于阀门的控制,至于水中掺不掺杂气泡则不是水龙头需要关心的。


所以,对于装饰器,可以简单地理解为是非侵入式的行为修改。


为什么要用装饰器


可能有些时候,我们会对传入参数的类型判断、对返回值的排序、过滤,对函数添加节流、防抖或其他的功能性代码,基于多个类的继承,各种各样的与函数逻辑本身无关的、重复性的代码。


函数中的作用


可以想像一下,我们有一个工具类,提供了一个获取数据的函数:


class Model1 {
  getData() {
    // 此处省略获取数据的逻辑
    return [{
      id: 1,
      name: 'Niko'
    }, {
      id: 2,
      name: 'Bellic'
    }]
  }
}


console.log(new Model1().getData())    // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
console.log(Model1.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]


现在我们想要添加一个功能,记录该函数执行的耗时。
因为这个函数被很多人使用,在调用方添加耗时统计逻辑是不可取的,所以我们要在Model1中进行修改:


class Model1 {
  getData() {
+  let start = new Date().valueOf()
+  try {
      // 此处省略获取数据的逻辑
      return [{
        id: 1,
        name: 'Niko'
      }, {
        id: 2,
        name: 'Bellic'
      }]
+  } finally {
+    let end = new Date().valueOf()
+    console.log(`start: ${start} end: ${end} consume: ${end - start}`)
+  }
  }
}


// start: XXX end: XXX consume: XXX
console.log(new Model1().getData())    // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
// start: XXX end: XXX consume: XXX
console.log(Model1.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]


这样在调用方法后我们就可以在控制台看到耗时的输出了。
但是这样直接修改原函数代码有以下几个问题:
1.统计耗时的相关代码与函数本身逻辑并无一点关系,影响到了对原函数本身的理解,对函数结构造成了破坏性的修改
2.如果后期还有更多类似的函数需要添加统计耗时的代码,在每个函数中都添加这样的代码显然是低效的,维护成本太高


所以,为了让统计耗时的逻辑变得更加灵活,我们将创建一个新的工具函数,用来包装需要设置统计耗时的函数。
通过将Class与目标函数的name传递到函数中,实现了通用的耗时统计:


function wrap(Model, key) {
  // 获取Class对应的原型
  let target = Model.prototype


  // 获取函数对应的描述符
  let descriptor = Object.getOwnPropertyDescriptor(target, key)


  // 生成新的函数,添加耗时统计逻辑
  let log = function (...arg) {
    let start = new Date().valueOf()
    try {
      return descriptor.value.apply(this, arg) // 调用之前的函数
    } finally {
      let end = new Date().valueOf()
      console.log(`start: ${start} end: ${end} consume: ${end - start}`)
    }
  }


  // 将修改后的函数重新定义到原型链上
  Object.defineProperty(target, key, {
    ...descriptor,
    value: log      // 覆盖描述符重的value
  })
}


wrap(Model1, 'getData')
wrap(Model2, 'getData')


// start: XXX end: XXX consume: XXX
console.log(new Model1().getData())    // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]
// start: XXX end: XXX consume: XXX
console.log(Model2.prototype.getData()) // [ { id: 1, name: 'Niko'}, { id: 2, name: 'Bellic' } ]


接下来,我们想控制其中一个Model的函数不可被其他人修改覆盖,所以要添加一些新的逻辑:


function wrap(Model, key) {
  // 获取Class对应的原型
  let target = Model.prototype


  // 获取函数对应的描述符
  let

首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/7/7
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇u-boot-1.1.6第1阶段分析之make s.. 下一篇Python 字节码介绍

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目