裝飾器來啦
前言
大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心。
基本介紹
裝飾器是一種以 @ 符號開頭的特殊語法,放在目的碼的前面用於包裝或擴充套件程式碼功能。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(this, arguments);
// 在原始方法執行後加入邏輯
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(this, arguments);
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(this, arguments)
}, 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(this, arguments);
setTimeout(() => {
isLock = false;
}, time);
}
return descriptor;
}
}
classC{
@debounce(1000)
onClick() {}
@throttle(1000)
onScroll() {}
}
結語
我是林三心
一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;
一個偏前端的全幹工程師;
一個不正經的金塊作者;
逗比的B站up主;
不帥的小紅書博主;
喜歡打鐵的籃球菜鳥;
喜歡歷史的乏味少年;
喜歡rap的五音不全弱雞
如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 -->