前言

美国大选又来了,比二人转好看多了,有一段听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能让你的代码更加整洁,并且能够让类在他们之间共用一些逻辑~妙哉妙哉