likes
comments
collection
share

理解 JS Decorator装饰器

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

最近学习node的服务端框架nest, 发现装饰器语法无处不在,比如controller中编写接口路径

理解 JS Decorator装饰器 使用各种AOP切面

理解 JS Decorator装饰器 其实不止Nest, Vue,MobX等也是广泛的使用装饰器语法,所以学会装饰器势在必行。

很久以前,看过es6中关于装饰器的讲解,学了不用就会很快忘记,趁着这次,再复习一遍装饰器的概念和用法。

装饰器是什么?

装饰器是一个普通的函数,使用@标记。顾名思义,就是对目标起到装饰作用,可以在不修改原始代码的情况下为其添加新的行为。只能用于装饰类和类的属性以及类的方法。简而言之,装饰器就是用来扩展类和类的属性功能的。

类是作为一个参数传入到装饰器中

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

注意:** 装饰的过程在代码编译时发生**

用法

装饰器一共三种用法:

  • 装饰类
  • 装饰类的属性
  • 装饰类的方法

装饰类

可以给类添加静态属性,静态方法,通过修改原型添加类的实例属性和实例方法。类作为装饰器的唯一参数

定义一个空类A, 通过装饰器添加其属性和方法

 function decorator(target) {
    // target: 被装饰的类 class A {}
    target.test = '123' // 添加静态属性
    target.testfunc = () => {
        return 'testfunc'
    } // 添加静态方法
    target.prototype.testp = '456' // 添加实例属性
    target.prototype.testpf = () => {
        return 'testpf'
    } // 添加实例方法
        
}

@decorator
class A {}
const a = new A()
console.log(a.testp) // '456'
console.log(a.testpf()) // 'testpf'
console.log(A.test) // '123'
console.log(A.testfunc()) // 'testfunc'

如果需要从装饰器函数调用时传递参数,可以在装饰器函数中返回一个函数:

function decorator(str) {
    return (target) => {
        target.prototype.test = str
    }
}

@decorator('abc')
class A {}
const a = new A()
console.log(a.test) // 'abc'

装饰类的属性

装饰器函数可以接收三个参数:

  • 第一个参数: 被装饰的目标所在的类的原型
  • 第二个参数: 被装饰的目标名
  • 第三个参数: 被装饰的目标的属性描述符

在类中我们经常会看到一个属性只读,如下:

function readonly(target, name, descriptor) {
    // target: 被装饰的类 class B {}
    // name: 被装饰的目标名 ele
    // descriptor: 被装饰的目标的属性描述符
    descriptor.writable = false 
}
class B {
    @readonly ele = '123' // 会报错
}

装饰类的方法

接收的参数和装饰属性一致

在这里,我们定义一个类C, C中有一个方法,需要在方法返回的结果值末尾加上一些字符串:

function concatStr(arg) {
    return (target, name, descriptor) => {
        const old = descriptor.value
        descriptor.value = function (...args) {
            return old.apply(this, [...args]) + arg
        }
    }
}
class C {
    @concatStr('how are you?')
    sum(a, b) {
        return a + b
    }
}
const c = new C()
console.log(c.sum('hello', 'i am xiaoming ,')) // 'hello i am xiaoming, how are you?'

注意: 装饰器不能装饰普通的函数,因为普通函数存在变量声明提升。

应用

  • 节流防抖函数 有了装饰器,我们只需要在函数前通过@标记一个装饰器函数就可以了,通俗易懂。 这里实现防抖函数,节流函数类似。

    debounce.js

  function debounce(delay) {
    let timer = null;
    return function (target, name, descriptor) {
        const originFn = descriptor.value;
        descriptor.value = function (...args) {
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                originFn.apply(this, args);
            }, delay);
        }
        return descriptor;
    }
 class TestDebounce {
    @debounce(2000)
    handleMouseMove(val) {
        console.log('handleMouseMove', val)
    }
 }

页面上监听mousemove事件

 const p = new TestDebounce();
 window.addEventListener('mousemove', p.handleMouseMove.bind(null, 'hahaha'));

Over