當前位置: 妍妍網 > 碼農

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多名前端小夥伴在等著一起學習哦 -->