likes
comments
collection
share

对装饰器的小小理解

作者站长头像
站长
· 阅读数 54

装饰器

装饰器并没有正实纳入JS的语法当中,当前还处于草案阶段。以目前的情况来看,真正纳入语法当中是非常难的。但是在TS中我们可以放心的使用装饰器。 装饰器可以对进行装饰。

什么是装饰器

对于装饰器,我们可以简单的将其理解为一个类的一个预处理。在真正代码执行前会对类进行一个加工。

类的装饰

// 定义装饰器函数
function TestDecorator(target){
  target.isVisible = true
}

@TestDecorator  //使用装饰器
class Test {
  //....
}

//代码等价于:
class Test {
  //....
}
Test = TestDecorator(Test) || Test

在使用装饰器之前需要定义一个装饰器函数,该函数有一个参数target,该参数指向需要进行装饰的类或者函数。这里指向的是类。 想对哪个类使用装饰器,只需要在该类前面使用@加上装饰器函数名即可,此时对应装饰器函数中的target就指向该类。这里是对Test类使用装饰器 TestDecorator,因此TestDecoratortarget就指向Test类。然后执行装饰器中代码,这里是将Test类添加了isVisible属性。

装饰器的参数

在使用装饰器时,我们同样也可以向内部传入参数,例如:

// 定义可传参装饰器
function decorator(arg){
  return function _decorator (target){
    target.isVisible = arg
  }
}

@decorator(false) //使用装饰器时传入参数
class Test {

}

可传入参数装饰器很好理解,可以暂时认为内部返回的函数才是真正的装饰器,而外部decorator函数的作用是通过闭包存储传入的参数。 上面代码可以等价为:

function decorator(arg){
  return function _decorator (target){
    target.isVisible = arg
  }
}

@decorator(false) // 等价于先执行decorator函数,执行完函数返回_decorator。然后再@_decorator
class Test {

}

这样就可以实现向装饰器内部传参。 注意装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。 装饰器函数的执行时在代码编译阶段而不是代码运行阶段。对于编译阶段的函数执行,我们可简单的等同于运行阶段,但是编译阶段的一些数据的初始化可能还没有完成,因此对于数据的使用需要注意。

想要对类的实例添加属性,我们可以这样做:

// mixins.js
export function Mixins(...list){
  return function (target){
    Object.assign(target.prototype,...list)
  }
}

// index.js
import { Mixins } from './mixins.js'

const obj = {
  fnc(){}
}
@Mixins(obj)
class Test {

}
let test = new Test()
test.fnc()

类方法的装饰

装饰器不仅可以装饰类,同时还可以装饰类中的属性,这里装饰的是类的方法。

class Test {
  @readonly
  name(){   
    return 'xz'
  }
}

装饰器函数 readonly 一共可以接受三个参数。

function readonly(target, name, descriptor){
  descriptor.writable = false
  return descriptor
}
  • target:类的原型对象,例如Test.prototype,在这里装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身)。
  • name:所要装饰的属性名。
  • descriptor:对属性的描述对象。 另外,上面代码说明,装饰器readonly会修改属性的 描述对象descriptor,然后被修改的描述对象再用来定义属性。

多装饰器

装饰器可以同时运用多个:

@decorator1
@decorator2
class Test {

}

执行的顺序为decorator2 -> decorator1,离class定义最近的先执行。 可以想像成函数嵌套的形式:

decorator1(decorator2(class Test {}))

为什么装饰器不能用于函数

装饰器只能用于类和类的方法,不能用于函数,因为函数在编译阶段存在变量提示。我们举个小栗子:

var count = 0
var add = function (){
  count++
}
@add
function foo(){}

上面的代码本意是使执行后的count变为1,但是实际上count的值为0,原因就出在编译阶段。 在编译阶段JS会存在变量提升的问题,那么此时上面的代码在编译器的执行顺序中就变为了:

@add
function foo(){}  //将foo函数提升至顶部,此时执行对应的add装饰器函数,但因为此时add装饰器函数并未初始化,因此无法执行

var count   //声明count
var add     //声明add
count = 0   //赋初值
add = function(){     //赋初值,到这里才为add赋初值,但是已经没有执行机会了
  count++
}

如果将函数换为类,就不会出现上面情况,因为类不存在变量提升:

var count   //声明count
var add     //声明add
count = 0   //赋初值
add = function(){     //赋初值
  count++
}

@add    //执行add装饰器函数,此时count变为1
class foo{}

文献参考:

转载自:https://juejin.cn/post/7134714722158379038
评论
请登录