js中的Decorator

Javascript Sep 30, 2020

前言

美国大选又来了,比二人转好看多了,有一段听biden说他儿子在伊拉克咋的咋的,然后川川一直在打断biden,我就听biden一直念叨my son, my son的,正在跑步的我以为开始飙脏话了~不知道这次debate我川会说出多少个China来~那么今天我们就结合川川来用一下js中的decorator来实现一个川普的Class,并对这个class的方法 做一下debounce 和 throttle。

什么是decorator(装饰器)

顾名思义,就是用来装饰你输入的东西的(这个东西可以是类,可以是类的属性),其实可以理解成就是一个wrapper(not rapper)。
在ES6之前没有Class的概念,所以装饰器没啥作用,在ES6提出之后呢,你会发现比方说我们有多个类需要一些共享的行为之后呢,这个装饰器就会让你的代码更优雅一些。最早接触decorator其实应该是在ts当中,后来有一次面试被问到了装饰器的问题。万万没想到js中也有。

使用方法

因为现在浏览器中还是无法使用decorator的语法,还处于stage2貌似,所以需要加一个babel的插件,在webpack的js rules中加入这个插件@babel/plugin-proposal-decorators并安装。

{
   test: /\.js$/,
   exclude: /(node_modules|bower_components)/,
   use: {
       loader: 'babel-loader',
       options: {
          presets: ['@babel/preset-env'],
          plugins:[['@babel/plugin-proposal-decorators',  { legacy: true }]]
       }
   }
}

然后就可以愉快的玩耍了~
简单理解decorator其实就是一个方法,将输入的东西进行装饰。如下面的代码:

@decorator
class Trump{
    // Trmup是被修饰的类
    @decorator
    Shouting(){console.log('China!')}
    // Shouting是被修饰的方法

}

// decorator就是修饰器
function decorator(target, key, descriptor){
}

使用很简单,就是加上@decoratorName以后就能够对下面的东西进行修饰。
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。---本句话复制自阮一峰的ECMAScript6入门

修饰类

// Trump是被修饰的类
@decorator
class Trump{
   shouting(){console.log(`make ${this.country} great again`)}
}

function decorator(target, key, descriptor){
   // 添加静态属性
   target.identification = 'Chinese'
   // 为实例添加属性
   target.prototype.country = 'China'
}

console.log(Trump.identification)
// 输出Chinese

let trump = new Trump()
trump.shouting()
// 输出make China great again
console.log(trump.country)
//输出China
  • 当修饰器修饰类的时候三个参数target, key, descriptor后面两个都是undefine,只有第一个参数target是class自己,所以我们先往类上添加了一个静态属性indentification
  • 又因为Trump.prototype === trump.__proto__, 所以使用target.prototype往实例trump的原型链上添加了属性country
  • 实例的方法shouting调用了this.country,最终输出"make China great again" 。thank you president Trump!

修饰类中的属性/方法

下面我们再次使用trump类,其中有三个方法,分别为shouting, repeating, throttle

  • repeating方法做个debounce,频率太快就闭嘴
  • yielding方法做个截流,每秒只允许说一次
  • 其中我们让shouting方法不可更改
class Trump{
   @readonly
   shouting(){console.log(`sleepy joe`)}

   @debounce(2000)
   // 如果2s内重复说的话就禁言
   repeating(){console.log('China!')}

   @throttle(1000)
   // 频率太快的只允许1s说一次
   yielding(){console.log(`Fake news!`)}

}

function readonly(target, key, descriptor){
    descriptor.writable = false
    descriptor.enumerable = true
    descriptor.configurable = true
    return descriptor
}

function debounce(time){
   return function(target, key, descriptor) {
      let debounceId = true
      let func = descriptor.value
      descriptor.value = () => {
         clearTimeout(debounceId)
         debounceId = setTimeout(() => {
            func.call(this)
         }, time)
      }
   }
}

function throttle(time){
   return function(target, key, descriptor) {
      let throttleId = null
      let func = descriptor.value
      descriptor.value = () => {
          if(throttleId) return
          throttleId = setTimeout(() => {
              func.call(this)
              throttleId = null
          }, time) 
      }
   }
}

let trump = new Trump()

setInterval(() => {
// debounce
   trump.repeating()
}, 200)
// 频率太快,没有打印China

setInterval(() => {
// throttle
   trump.yielding() // fake news
}, 200)
// 每1秒才打印一次

trump.shouting = () => {
   console.log('sleepy Trump')
}
// 无法改变shouting的value
// Uncaught TypeError: Cannot assign to read only property 'shouting' of object '#<Trump>'

与装饰类不同,当我们装饰类中的属性或者方法的时候,装饰器的三个参数全部有值,

  • target实际上是类的原型对象,也就是Trump.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身)---本句话复制自阮一峰的ECMASript6。
  • key是方法名字,
  • descriptor实际上就是Object.defineProperty(Trump.prototype, "shouting", descriptor)中的descriptor。

Final

decorator能让你的代码更加整洁,并且能够让类在他们之间共用一些逻辑~妙哉妙哉

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.