Decorators: เพิ่ม Meta Programming ใน JavaScript
ในบทก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับ getter และ setter methods ซึ่งเป็นเครื่องมือที่มีประโยชน์ในการควบคุมการเข้าถึงและแก้ไข properties ของ object ในบทนี้ เราจะก้าวไปอีกขั้นด้วยการเรียนรู้เกี่ยวกับ Decorators ซึ่งเป็นคุณสมบัติที่ช่วยให้เราสามารถเพิ่ม meta programming ให้กับคลาสและ methods ของเราได้
Decorators เป็นคุณสมบัติที่ยังอยู่ในช่วง Proposal (Stage 3) ใน ECMAScript แต่ได้รับการสนับสนุนอย่างกว้างขวางจากเครื่องมือต่างๆ เช่น TypeScript และ Babel (พร้อม plugin ที่เหมาะสม) Decorators ช่วยให้เราสามารถปรับเปลี่ยนหรือเพิ่มความสามารถให้กับคลาส, methods, properties, หรือ parameters ได้โดยไม่ต้องแก้ไขโค้ดเดิมโดยตรง ทำให้โค้ดของเรามีความ modular และบำรุงรักษาง่ายขึ้น
Decorator Syntax
Decorator syntax ใน JavaScript ใช้เครื่องหมาย @ ตามด้วยชื่อของ decorator ที่ต้องการนำไปใช้ Decorator จะถูกวางไว้เหนือส่วนของโค้ดที่ต้องการตกแต่ง (decorate) เช่น คลาส, method, หรือ property
@decoratorName
class MyClass {
@decoratorName
myMethod() {
// ...
}
}
Class Decorators
Class decorators เป็น decorators ที่ใช้กับคลาส มีหน้าที่ในการปรับเปลี่ยนหรือเพิ่มความสามารถให้กับคลาสนั้นๆ Class decorator จะรับ constructor function ของคลาสเป็น argument และสามารถ return constructor function ใหม่เพื่อแทนที่ constructor function เดิมได้
เหตุผลเชิงออกแบบ: Class decorators ช่วยให้เราสามารถเพิ่ม functionality ข้ามคลาสต่างๆ ได้อย่างง่ายดาย เช่น การ logging, การ authorization, หรือการ dependency injection โดยไม่ต้องแก้ไขโค้ดของแต่ละคลาสโดยตรง
ตัวอย่าง:
function logClass(constructor) {
return class extends constructor {
constructor(...args) {
super(...args);
console.log(`Creating a new instance of ${constructor.name}`);
}
};
}
@logClass
class User {
constructor(name) {
this.name = name;
}
}
const user = new User("John"); // Output: Creating a new instance of User
ในตัวอย่างนี้ logClass เป็น class decorator ที่รับ constructor ของคลาส User เป็น argument จากนั้น return คลาสใหม่ที่ extends จาก constructor เดิม และเพิ่ม logging message ใน constructor ของคลาสใหม่ เมื่อเราสร้าง instance ของ User จะมี logging message ปรากฏใน console
Trade-offs: การใช้ class decorators อาจทำให้โค้ดซับซ้อนขึ้นเล็กน้อย โดยเฉพาะอย่างยิ่งเมื่อมี decorators หลายตัวซ้อนกัน อย่างไรก็ตาม ข้อดีของการ modularity และการ reuseability มักจะคุ้มค่ากว่าข้อเสีย
Method Decorators
Method decorators เป็น decorators ที่ใช้กับ methods มีหน้าที่ในการปรับเปลี่ยนหรือเพิ่มความสามารถให้กับ method นั้นๆ Method decorator จะรับ arguments 3 ตัว:
target: Prototype ของคลาสpropertyKey: ชื่อของ methoddescriptor: Property descriptor ของ method
Property descriptor เป็น object ที่มี properties เช่น value (function ของ method), writable (boolean ว่า method สามารถเขียนทับได้หรือไม่), enumerable (boolean ว่า method จะถูกแสดงใน loop หรือไม่), และ configurable (boolean ว่า descriptor สามารถแก้ไขได้หรือไม่)
เหตุผลเชิงออกแบบ: Method decorators ช่วยให้เราสามารถเพิ่ม functionality เฉพาะเจาะจงให้กับแต่ละ method ได้ เช่น การ caching, การ profiling, หรือการ authorization
ตัวอย่าง:
function logMethod(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
console.log(`Calling method ${propertyKey} with arguments: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a, b) {
return a + b;
}
}
const calculator = new Calculator();
calculator.add(2, 3);
// Output:
// Calling method add with arguments: 2,3
// Method add returned: 5
ในตัวอย่างนี้ logMethod เป็น method decorator ที่รับ target, propertyKey, และ descriptor เป็น arguments จากนั้นแก้ไข descriptor.value ให้เป็น function ใหม่ที่ logging การเรียกใช้ method และผลลัพธ์ของ method เดิม เมื่อเราเรียกใช้ calculator.add(2, 3) จะมี logging messages ปรากฏใน console
Trade-offs: การใช้ method decorators อาจทำให้ debugging ยากขึ้นเล็กน้อย เนื่องจากโค้ดที่ถูก execute จริงๆ อาจไม่ตรงกับโค้ดที่เราเขียนโดยตรง อย่างไรก็ตาม เครื่องมือ debugging ที่ดีมักจะสามารถ handle decorators ได้อย่างเหมาะสม
แนวปฏิบัติที่ดีในการใช้ Decorators
- ใช้ decorators อย่างมีสติ: Decorators เป็นเครื่องมือที่มีประสิทธิภาพ แต่ควรใช้เฉพาะเมื่อจำเป็นจริงๆ เท่านั้น การใช้ decorators มากเกินไปอาจทำให้โค้ดซับซ้อนและอ่านยาก
- เขียน decorators ที่ reuseable: พยายามเขียน decorators ที่สามารถนำไปใช้กับคลาสและ methods ต่างๆ ได้อย่างหลากหลาย
- Document decorators อย่างชัดเจน: อธิบายว่า decorator ทำอะไร และมีผลกระทบอย่างไรต่อโค้ดที่ถูก decorate
- ทดสอบ decorators อย่างละเอียด: ตรวจสอบให้แน่ใจว่า decorators ทำงานได้อย่างถูกต้อง และไม่ทำให้เกิด side effects ที่ไม่คาดฝัน
ข้อควรระวัง: เนื่องจาก Decorators ยังเป็นคุณสมบัติที่อยู่ในช่วง proposal การใช้งานจริงอาจแตกต่างกันไปขึ้นอยู่กับเครื่องมือและ environment ที่ใช้ ควรตรวจสอบ documentation ของ TypeScript หรือ Babel อย่างละเอียดก่อนใช้งาน
ในบทนี้ เราได้เรียนรู้เกี่ยวกับ Decorators ซึ่งเป็นคุณสมบัติที่ช่วยให้เราสามารถเพิ่ม meta programming ให้กับคลาสและ methods ของเราได้ Decorators ช่วยให้โค้ดของเรามีความ modular และบำรุงรักษาง่ายขึ้น ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ WeakMap และ WeakSet ซึ่งเป็น data structures ที่มีประโยชน์ในการจัดการ memory ใน JavaScript