JavaScript裝飾器從基礎(chǔ)到實(shí)戰(zhàn)教程
裝飾器(Decorator)是JavaScript中一種??聲明式??的語(yǔ)法特性,用于在不修改原始代碼的情況下,動(dòng)態(tài)擴(kuò)展類、方法、屬性或參數(shù)的行為。它本質(zhì)上是一個(gè)??函數(shù)??,通過(guò)@符號(hào)附加到目標(biāo)上,讓代碼更優(yōu)雅、可維護(hù)。本文將從基礎(chǔ)概念入手,逐步講解裝飾器的類型、用法、進(jìn)階技巧及實(shí)戰(zhàn)場(chǎng)景。
一、裝飾器基礎(chǔ)概念
1.1 什么是裝飾器?
裝飾器是一種??特殊函數(shù)??,接收目標(biāo)對(duì)象(類、方法、屬性等)作為參數(shù),返回修改后的目標(biāo)或新的描述符。其核心價(jià)值是??分離關(guān)注點(diǎn)??——將日志記錄、權(quán)限驗(yàn)證、性能監(jiān)控等功能從業(yè)務(wù)代碼中剝離,通過(guò)裝飾器“裝飾”到目標(biāo)上。
1.2 裝飾器的語(yǔ)法
裝飾器使用@符號(hào),緊跟裝飾器函數(shù)名,放置在目標(biāo)聲明前:
@decorator
class MyClass {} // 類裝飾器
class MyClass {
@decorator
myMethod() {} // 方法裝飾器
@decorator
myProperty; // 屬性裝飾器
}等價(jià)于:
class MyClass {}
MyClass = decorator(MyClass) || MyClass; // 類裝飾器
MyClass.prototype.myMethod = decorator(MyClass.prototype, 'myMethod', Object.getOwnPropertyDescriptor(MyClass.prototype, 'myMethod')) || MyClass.prototype.myMethod; // 方法裝飾器1.3 裝飾器的執(zhí)行時(shí)機(jī)
裝飾器在??編譯階段??執(zhí)行(ES6模塊加載時(shí)),而非運(yùn)行時(shí)。例如:
function logClass(target) {
console.log('類裝飾器執(zhí)行'); // 編譯階段輸出
target.prototype.timestamp = new Date();
}
@logClass
class MyClass {}
// 實(shí)例化時(shí)不會(huì)再次執(zhí)行裝飾器
const instance = new MyClass();
console.log(instance.timestamp); // 輸出編譯階段的日期二、裝飾器的主要類型
裝飾器可分為五大類,分別作用于不同的目標(biāo):
2.1 類裝飾器
??作用??:修改類的構(gòu)造函數(shù)或原型,添加靜態(tài)屬性/方法或?qū)嵗龑傩?方法。
??參數(shù)??:類的構(gòu)造函數(shù)(target)。
??示例??:為類添加靜態(tài)屬性和實(shí)例屬性。
function addStaticProperty(staticProp, value) {
return function(target) {
target[staticProp] = value; // 添加靜態(tài)屬性
};
}
function addInstanceProperty(instanceProp, initialValue) {
return function(target) {
target.prototype[instanceProp] = initialValue; // 添加實(shí)例屬性
};
}
@addStaticProperty('version', '1.0.0')
@addInstanceProperty('timestamp', new Date())
class MyClass {}
console.log(MyClass.version); // 輸出: 1.0.0
const instance = new MyClass();
console.log(instance.timestamp); // 輸出: 編譯階段的日期2.2 方法裝飾器
??作用??:修改方法的描述符(value、writable等),實(shí)現(xiàn)日志、權(quán)限、性能監(jiān)控等功能。
??參數(shù)??:
target:靜態(tài)方法為類的構(gòu)造函數(shù),實(shí)例方法為類的原型;propertyKey:方法名;descriptor:方法描述符(包含value、writable等屬性)。
??示例??:記錄方法調(diào)用日志。
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`調(diào)用方法 ${propertyKey},參數(shù): ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 返回: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// 輸出:
// 調(diào)用方法 add,參數(shù): [2,3]
// 方法 add 返回: 52.3 屬性裝飾器
??作用??:修改屬性的描述符(如writable設(shè)為false實(shí)現(xiàn)只讀),或通過(guò)Object.defineProperty定義屬性的getter/setter。
??參數(shù)??:
target:靜態(tài)屬性為類的構(gòu)造函數(shù),實(shí)例屬性為類的原型;propertyKey:屬性名。
??示例??:實(shí)現(xiàn)屬性只讀。
function readOnly(target, propertyKey) {
Object.defineProperty(target, propertyKey, {
writable: false, // 設(shè)為不可寫(xiě)
configurable: false // 設(shè)為不可配置(防止后續(xù)修改)
});
}
class Person {
@readOnly
name = 'John';
}
const person = new Person();
person.name = 'Jane'; // 嚴(yán)格模式下報(bào)錯(cuò):Cannot assign to read only property 'name'
console.log(person.name); // 輸出: John2.4 訪問(wèn)器裝飾器
??作用??:修改屬性的getter或setter方法,實(shí)現(xiàn)數(shù)據(jù)格式化、驗(yàn)證等功能。
??參數(shù)??:與方法裝飾器相同(target、propertyKey、descriptor)。
??示例??:將屬性值轉(zhuǎn)為大寫(xiě)。
function capitalize(target, propertyKey, descriptor) {
const originalGetter = descriptor.get;
descriptor.get = function() {
return originalGetter.call(this).toUpperCase(); // 轉(zhuǎn)為大寫(xiě)
};
return descriptor;
}
class User {
constructor(name) {
this._name = name;
}
@capitalize
get name() {
return this._name;
}
}
const user = new User('john');
console.log(user.name); // 輸出: JOHN2.5 參數(shù)裝飾器
??作用??:為方法參數(shù)添加元數(shù)據(jù)(如參數(shù)名、驗(yàn)證規(guī)則),常用于框架中的依賴注入或參數(shù)校驗(yàn)。
??參數(shù)??:
target:類的原型(實(shí)例方法)或構(gòu)造函數(shù)(靜態(tài)方法);propertyKey:方法名;parameterIndex:參數(shù)的索引(從0開(kāi)始)。
??示例??:記錄參數(shù)索引。
function logParameter(target, propertyKey, parameterIndex) {
const existingParameters = target[propertyKey + 'Parameters'] || [];
existingParameters.push(parameterIndex);
target[propertyKey + 'Parameters'] = existingParameters;
}
class UserService {
getUser(@logParameter id, @logParameter name) {
return { id, name };
}
}
const userService = new UserService();
userService.getUser(1, 'John');
console.log(UserService.prototype.getUserParameters); // 輸出: [0, 1]三、裝飾器的高級(jí)用法
3.1 裝飾器工廠:傳遞參數(shù)
裝飾器本身是靜態(tài)的,若需動(dòng)態(tài)配置,可通過(guò)??裝飾器工廠??(返回裝飾器函數(shù)的函數(shù))實(shí)現(xiàn)。
??示例??:帶參數(shù)的權(quán)限裝飾器。
function checkPermission(requiredRole) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const userRole = this.userRole; // 假設(shè)實(shí)例上有userRole屬性
if (userRole === requiredRole) {
return originalMethod.apply(this, args);
} else {
throw new Error(`無(wú)權(quán)限:需要${requiredRole}角色`);
}
};
return descriptor;
};
}
class AdminPanel {
userRole = 'admin';
@checkPermission('admin')
deleteUser() {
console.log('用戶已刪除');
}
}
const panel = new AdminPanel();
panel.deleteUser(); // 輸出: 用戶已刪除
const userPanel = new AdminPanel();
userPanel.userRole = 'user';
userPanel.deleteUser(); // 報(bào)錯(cuò): 無(wú)權(quán)限:需要admin角色3.2 多個(gè)裝飾器的執(zhí)行順序
多個(gè)裝飾器按??從外到內(nèi)??的順序應(yīng)用(聲明順序),但??從內(nèi)到外??執(zhí)行(執(zhí)行順序)。
??示例??:
function dec1(target, propertyKey, descriptor) {
console.log('裝飾器1應(yīng)用');
return descriptor;
}
function dec2(target, propertyKey, descriptor) {
console.log('裝飾器2應(yīng)用');
return descriptor;
}
class Example {
@dec1
@dec2
method() {}
}
// 輸出:
// 裝飾器2應(yīng)用
// 裝飾器1應(yīng)用3.3 組合裝飾器:簡(jiǎn)化重復(fù)代碼
通過(guò)高階函數(shù)組合多個(gè)裝飾器,減少重復(fù)代碼。
??示例??:組合日志和性能監(jiān)控裝飾器。
function composeDecorators(...decorators) {
return function(target, propertyKey, descriptor) {
return decorators.reduceRight((desc, decorator) => decorator(target, propertyKey, desc), descriptor);
};
}
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`調(diào)用方法 ${propertyKey}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
function measureTime(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const start = Date.now();
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 執(zhí)行時(shí)間: ${Date.now() - start}ms`);
return result;
};
return descriptor;
}
const logAndMeasure = composeDecorators(logMethod, measureTime);
class DataProcessor {
@logAndMeasure
processData(data) {
// 模擬耗時(shí)操作
for (let i = 0; i < 1e6; i++) {}
return data;
}
}
const processor = new DataProcessor();
processor.processData([1, 2, 3]);
// 輸出:
// 調(diào)用方法 processData
// 方法 processData 執(zhí)行時(shí)間: 5ms四、裝飾器的實(shí)戰(zhàn)場(chǎng)景
4.1 日志記錄
通過(guò)裝飾器自動(dòng)記錄方法的調(diào)用信息和返回值,無(wú)需手動(dòng)添加console.log。
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`[LOG] 調(diào)用 ${propertyKey},參數(shù): ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`[LOG] ${propertyKey} 返回: ${result}`);
return result;
};
return descriptor;
}
class LoggerExample {
@logMethod
greet(name) {
return `Hello, ${name}!`;
}
}
const logger = new LoggerExample();
logger.greet('Alice');
// 輸出:
// [LOG] 調(diào)用 greet,參數(shù): ["Alice"]
// [LOG] greet 返回: Hello, Alice!4.2 權(quán)限驗(yàn)證
通過(guò)裝飾器在方法執(zhí)行前檢查用戶權(quán)限,避免重復(fù)編寫(xiě)權(quán)限邏輯。
function hasPermission(requiredPermission) {
return function(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const userPermissions = this.permissions || []; // 假設(shè)實(shí)例上有permissions屬性
if (userPermissions.includes(requiredPermission)) {
return originalMethod.apply(this, args);
} else {
throw new Error(`無(wú)權(quán)限:需要${requiredPermission}`);
}
};
return descriptor;
};
}
class DocumentManager {
permissions = ['read'];
@hasPermission('write')
editDocument(content) {
console.log('文檔已編輯');
}
}
const manager = new DocumentManager();
manager.editDocument('新內(nèi)容'); // 報(bào)錯(cuò): 無(wú)權(quán)限:需要write
manager.permissions.push('write');
manager.editDocument('新內(nèi)容'); // 輸出: 文檔已編輯4.3 性能監(jiān)控
通過(guò)裝飾器監(jiān)控方法的執(zhí)行時(shí)間,快速定位性能瓶頸。
function measurePerformance(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`[PERF] 方法 ${propertyKey} 執(zhí)行時(shí)間: ${end - start}ms`);
return result;
};
return descriptor;
}
class HeavyCalculation {
@measurePerformance
calculateFactorial(n) {
return n <= 1 ? 1 : n * this.calculateFactorial(n - 1);
}
}
const calculator = new HeavyCalculation();
calculator.calculateFactorial(10); // 輸出: [PERF] 方法 calculateFactorial 執(zhí)行時(shí)間: 1ms4.4 自動(dòng)綁定this
通過(guò)裝飾器自動(dòng)綁定方法的this,避免在回調(diào)中丟失this。
function autobind(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
return originalMethod.apply(this, args);
};
return descriptor;
}
class Button {
constructor(text) {
this.text = text;
this.handleClick = this.handleClick.bind(this); // 傳統(tǒng)方式
}
@autobind
handleClick() {
console.log(`按鈕文本: ${this.text}`);
}
}
const button = new Button('Click Me');
const clickHandler = button.handleClick;
clickHandler(); // 輸出: 按鈕文本: Click Me(無(wú)需手動(dòng)綁定)五、注意事項(xiàng)
- ??裝飾器的執(zhí)行順序??:多個(gè)裝飾器按從外到內(nèi)應(yīng)用,從內(nèi)到外執(zhí)行。例如
@A @B等同于A(B(target)),但執(zhí)行順序是B先于A。 - ??不能裝飾函數(shù)??:函數(shù)存在變量提升,裝飾器可能在函數(shù)聲明前執(zhí)行,導(dǎo)致意外結(jié)果。建議使用類或箭頭函數(shù)。
- ??兼容性問(wèn)題??:裝飾器是ES提案,需通過(guò)Babel(
@babel/plugin-proposal-decorators)或TypeScript(experimentalDecorators)轉(zhuǎn)譯。 - ??保持單一職責(zé)??:每個(gè)裝飾器只做一件事(如日志、權(quán)限),避免復(fù)雜邏輯,提高可維護(hù)性。
六、總結(jié)
裝飾器是JavaScript中強(qiáng)大的語(yǔ)法糖,通過(guò)聲明式的方式擴(kuò)展類、方法、屬性的功能,讓代碼更清晰、可維護(hù)。本文從基礎(chǔ)概念講起,覆蓋了類、方法、屬性、訪問(wèn)器、參數(shù)五大類裝飾器,以及裝飾器工廠、組合裝飾器等高級(jí)用法,最后結(jié)合實(shí)戰(zhàn)場(chǎng)景演示了日志、權(quán)限、性能監(jiān)控等常見(jiàn)應(yīng)用。掌握裝飾器,能讓你寫(xiě)出更優(yōu)雅、更模塊化的代碼。
到此這篇關(guān)于JavaScript裝飾器從基礎(chǔ)到實(shí)戰(zhàn)教程的文章就介紹到這了,更多相關(guān)JavaScript裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java inputstream和outputstream使用詳解
這篇文章主要介紹了Java inputstream和outputstream使用詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Java按時(shí)間梯度實(shí)現(xiàn)異步回調(diào)接口的方法
這篇文章主要介紹了Java按時(shí)間梯度實(shí)現(xiàn)異步回調(diào)接口,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08
Java 實(shí)現(xiàn)將List平均分成若干個(gè)集合
這篇文章主要介紹了Java 實(shí)現(xiàn)將List平均分成若干個(gè)集合,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
使用SpringBoot+EasyExcel+Vue實(shí)現(xiàn)excel表格的導(dǎo)入和導(dǎo)出詳解
這篇文章主要介紹了使用SpringBoot+VUE+EasyExcel?整合導(dǎo)入導(dǎo)出數(shù)據(jù)的過(guò)程詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
Java基于Rest?Assured自動(dòng)化測(cè)試接口詳解
Rest Assured 是一個(gè)基于 Java 的流行的用于測(cè)試 RESTful API 的庫(kù)。這篇文章主要介紹了Java如何基于Rest?Assured實(shí)現(xiàn)自動(dòng)化測(cè)試接口,需要的可以參考一下2023-03-03
SpringBoot項(xiàng)目接收前端參數(shù)的11種方式
在前后端項(xiàng)目交互中,前端傳遞的數(shù)據(jù)可以通過(guò)HTTP請(qǐng)求發(fā)送到后端, 后端在Spring Boot中如何接收各種復(fù)雜的前端數(shù)據(jù)呢?這篇文章總結(jié)了11種在Spring Boot中接收前端數(shù)據(jù)的方式,需要的朋友可以參考下2024-12-12

