当前位置: 欣欣网 > 码农

5分钟带你了解【前端装饰器】,「高大上」的「基础知识」

2024-05-07码农

装饰器来啦

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心。

基本介绍

装饰器是一种以 @ 符号开头的特殊语法,放在目标代码的前面用于包装或扩展代码功能。JavaScript 的装饰器语法目前仍处于提案阶段,现阶段使用的话需要通过 bable 等方式进行编译之后,才能在浏览器正常运行。装饰器分为两种:类装饰器,类成员装饰器,分别用于装饰我们的类以及类的成员。

基本使用(类装饰器)

classMy class{
constructor() {}
}

比如现在有一个类或者函数 My class ,它的身上没有任何的东西,但是我想要给他加一个 log 方法,那我们应该怎么做呢?很多人回想说,直接在它身上加一个 log 方法即可~

classMy class{
constructor() {}
log() {}
}

但是这么做的话,一个 class 是能做,那如果要给 1000 个 class 加上 log 方法呢?那岂不是每一个都得写~很麻烦,这个时候可以使用 装饰器 去拓展每一个 class

  • 可以拓展原型方法

  • 可以拓展静态属性

  • 装饰器接收的参数是装饰的目标类,这里的 cls 就是 My class

    functionaddConcole(target{
    // 拓展原型方法
    target.prototype.log = function(msg{
    console.log(`[${newDate()}${msg}`);
    };
    // 拓展静态属性
    target.myName = '一个类'
    return target;
    }
    @addConcole
    classMy class{
    constructor() {}
    }
    const myObj = new My class();
    myObj.log('林三心');
    // [Sat Jul 08 2023 17:31:55 GMT+0800 (中国标准时间) 林三心
    console.log(My class.myName)
    // 一个类

    应用场景

    Node路由请求Url(类成员装饰器)

    我们在使用一些 Node 的框架时,在写接口的时候,我们可能会经常看到这样的代码

  • 当我们请求路径是 GET doc 时会匹配到findDocById

  • 当我们请求路径是 POST doc 时会匹配到createDoc

  • class Doc {
    @Get('doc')
    async findDocById(id) {}
    @Post('doc')
    async createDoc(data) {}
    }

    其实这个 @Get @Post ,是框架提供给我们的 类成员装饰器 ,是的,类成员也能使用装饰器,类成员装饰器接收三个参数:

  • target 是目标类的原型对象

  • key 表示目标类成员的键名

  • descriptor 是一个属性描述符对象,它包含目标类成员的属性特性(例如 value、writable 等)

  • functionGet(path{
    returnfunction(target, key, descriptor{
    console.log({
    target,
    key,
    descriptor
    })
    }
    }

    他的基本实现原理大概是这样的

    functionGet(routePath{
    returnfunction(target, key, descriptor{
    const originalMethod = descriptor.value; // 保存原始方法
    descriptor.value = function() {
    // 在原始方法执行前加入逻辑
    console.log('处理 Get 请求,路由路径: ' + routePath);
    // 执行原始方法
    const result = originalMethod.apply(thisarguments);
    // 在原始方法执行后加入逻辑
    console.log('Get 请求处理完成');
    return result;
    };
    return descriptor;
    };
    }



    接口权限控制(类成员装饰器叠加)

    上面我们介绍了一下 Nodejs Url 的路由匹配基本原理,但是这是不够的,因为很多接口还需要权限控制,比如:

  • GET doc 接口只能 管理员 才能访问

  • POST doc 接口只能 超级管理员 才能访问

  • 这也可以用装饰器来实现,并且装饰器是可以叠加的~

    classDoc{
    @Get('doc')
    @Role('admin')
    async findDocById(id) {}
    @Post('doc')
    @Role('superAdmin')
    async createDoc(data) {}
    }

    装饰器叠加的执行顺序是 从下往上 的~我们可以看一下下面的例子,发现输出顺序是

  • 2

  • 1

  • functionA () {
    console.log(1)
    }
    functionB () {
    console.log(2)
    }
    classDoc{
    @A
    @B
    async test() {}
    }

    至于权限控制的装饰器实现,需要根据不同业务去实现,我这里就粗略实现一下

    functionRole(permissions{
    returnfunction(target, key, descriptor{
    const originalMethod = descriptor.value; // 保存原始方法
    descriptor.value = function() {
    // 在原始方法执行前进行权限验证
    const user = getCurrentUser(); // 获取当前用户信息
    // 检查用户是否拥有所需权限
    const hasPermission = checkUserPermissions(user, permissions);
    if (!hasPermission) {
    // 如果用户没有权限,则抛出错误或执行其他处理
    thrownewError('无权限访问该接口');
    }
    // 执行原始方法
    const result = originalMethod.apply(thisarguments);
    return result;
    };
    return descriptor;
    };
    }




    记录日志的装饰器

    我们想要在执行某个函数的时候,记录一下

  • 函数调用时间

  • 函数调用参数

  • 这个时候我们也可以使用装饰器来完成,非常方便!!!

    // 日志装饰器函数
    functionlogDecorator(target, key, descriptor{
    const originalMethod = descriptor.value; // 保存原始方法
    descriptor.value = function(...args{
    console.log(`调用函数:${key}`);
    console.log(`参数:${JSON.stringify(args)}`);
    // 执行原始方法
    const result = originalMethod.apply(this, args);
    console.log(`返回值:${result}`);
    return result;
    };
    return descriptor;
    }
    // 示例类
    classExample{
    @logDecorator
    greet(name) {
    return`Hello, ${name}!`;
    }
    }
    // 测试
    const example = new Example();
    example.greet('林三心');





    缓存的装饰器

    如果我们执行一个方法,获取返回值需要经过一系列的计算,非常耗时间,那么我们可以判断入参,第一次时计算完缓存起来,第二次的时候如果还是这个入参,就直接从缓存中去拿,这个操作也可以使用装饰器去完成

    // 缓存装饰器函数
    functioncacheDecorator(target, key, descriptor{
    const cache = {}; // 缓存对象
    const originalMethod = descriptor.value; // 保存原始方法
    descriptor.value = function(...args{
    const cacheKey = JSON.stringify(args); // 生成缓存键
    if (cacheKey in cache) {
    console.log('从缓存中获取结果');
    return cache[cacheKey]; // 直接返回缓存结果
    }
    // 执行原始方法
    const result = originalMethod.apply(this, args);
    console.log('将结果缓存起来');
    cache[cacheKey] = result; // 缓存结果
    return result;
    };
    return descriptor;
    }
    // 示例类
    classExample{
    @cacheDecorator
    getValue(key) {
    console.log('执行函数逻辑');
    return key + Math.random(); // 模拟复杂的计算逻辑
    }
    }
    // 测试
    const example = new Example();
    console.log(example.getValue('foo'));
    console.log(example.getValue('foo')); // 从缓存中获取结果







    防抖节流的装饰器

    对于防抖节流,我们平时可能会这么去做

    classC{
    onClick = debounce(fn, 100)
    }

    但是这么做的话会使这个函数不好拓展,所以使用装饰器真的很方便

    // 防抖装饰器
    functiondebounce(time{
    returnfunction (target, key, descriptor{
    const oldFunction = descriptor.value;
    let timer = null;
    descriptor.value = function () {
    clearTimeout(timer);
    timer = setTimeout(() => {
    oldFunction.apply(thisarguments)
    }, time);
    };
    return descriptor;
    }
    }
    // 节流装饰器
    functionthrottle(time{
    returnfunction (target, key, descriptor{
    const oldFunction = descriptor.value;
    let isLock = false;
    descriptor.value = function() {
    if(isLock) { return; }
    isLock = true;
    oldFunction.apply(thisarguments);
    setTimeout(() => {
    isLock = false
    }, time);
    }
    return descriptor;
    }
    }
    classC{
    @debounce(1000)
    onClick() {}
    @throttle(1000)
    onScroll() {}
    }

    结语

    我是林三心

  • 一个待过 小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司 的作死型前端选手;

  • 一个偏前端的全干工程师;

  • 一个不正经的掘金作者;

  • 逗比的B站up主;

  • 不帅的小红书博主;

  • 喜欢打铁的篮球菜鸟;

  • 喜欢历史的乏味少年;

  • 喜欢rap的五音不全弱鸡

  • 如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 -->