对装饰器的小小理解
装饰器
装饰器并没有正实纳入JS
的语法当中,当前还处于草案阶段。以目前的情况来看,真正纳入语法当中是非常难的。但是在TS
中我们可以放心的使用装饰器。
装饰器可以对类进行装饰。
什么是装饰器
对于装饰器,我们可以简单的将其理解为一个类的一个预处理。在真正代码执行前会对类进行一个加工。
类的装饰
// 定义装饰器函数
function TestDecorator(target){
target.isVisible = true
}
@TestDecorator //使用装饰器
class Test {
//....
}
//代码等价于:
class Test {
//....
}
Test = TestDecorator(Test) || Test
在使用装饰器之前需要定义一个装饰器函数,该函数有一个参数target
,该参数指向需要进行装饰的类或者函数。这里指向的是类。
想对哪个类使用装饰器,只需要在该类前面使用@
加上装饰器函数名即可,此时对应装饰器函数中的target
就指向该类。这里是对Test
类使用装饰器
TestDecorator
,因此TestDecorator
中target
就指向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