JavaScript对象日志记录器实现咨询:装饰器方案是否可行?
Absolutely! Using a decorator (or a proxy-based approach, which is a modern, flexible alternative) is exactly the right strategy for building this logger. Let’s break down why, and walk through concrete implementations:
Why This Approach Works
Decorators and proxies let you wrap an existing object without modifying its original code—this follows the open/closed principle (open for extension, closed for modification). They preserve the original object’s behavior while injecting your logging logic seamlessly, which is perfect for your use case.
Implementation 1: Proxy-Based Logger (Best for Most Cases)
ES6 Proxies are ideal here because they can intercept all property accesses (including method calls) on both custom and built-in objects. This avoids having to manually iterate over methods or handle prototype chain quirks:
function createLoggingProxy(target) { return new Proxy(target, { get(target, prop, receiver) { const originalValue = Reflect.get(target, prop, receiver); // Only wrap function properties (methods) if (typeof originalValue === 'function') { return function(...args) { // Log the method call details console.log(`[LOG] Called method: "${String(prop)}"`); console.log(`[LOG] Arguments:`, args); // Execute the original method with correct context const result = originalValue.apply(this === receiver ? target : this, args); // Log the return value console.log(`[LOG] Method "${String(prop)}" returned:`, result); return result; }; } // Return non-function properties as-is return originalValue; } }); }
Example Usage
Custom Object
const myService = { calculateTotal(items) { return items.reduce((sum, item) => sum + item.price, 0); }, sendNotification(message) { return `Notification sent: ${message}`; } }; const loggedService = createLoggingProxy(myService); loggedService.calculateTotal([{ price: 10 }, { price: 20 }]); // Logs: // [LOG] Called method: "calculateTotal" // [LOG] Arguments: [{ price: 10 }, { price: 20 }] // [LOG] Method "calculateTotal" returned: 30
Built-In Object
Proxies work seamlessly with built-ins like arrays or dates:
const loggedArray = createLoggingProxy(['apple', 'banana']); loggedArray.push('cherry'); // Logs: // [LOG] Called method: "push" // [LOG] Arguments: ["cherry"] // [LOG] Method "push" returned: 3 console.log(loggedArray); // Still outputs: ["apple", "banana", "cherry"]
Implementation 2: Traditional Decorator Function
If you need to support older environments without Proxy support, a manual decorator that wraps each method works well for custom objects:
function logDecorator(target) { // Iterate over all own enumerable properties of the target for (const prop of Object.keys(target)) { const originalMethod = target[prop]; if (typeof originalMethod === 'function') { target[prop] = function(...args) { console.log(`[LOG] Called method: "${prop}" with args:`, args); const result = originalMethod.apply(this, args); console.log(`[LOG] Method "${prop}" returned:`, result); return result; }; } } return target; }
Note: This won’t automatically handle methods on the prototype chain (like built-in array methods), so it’s better suited for custom objects with own methods.
Alternative Approaches
Class Decorators: If your objects are instances of ES6 classes, you can decorate the class itself to add logging to all prototype methods:
function logClass(target) { for (const prop of Object.getOwnPropertyNames(target.prototype)) { const originalMethod = target.prototype[prop]; if (typeof originalMethod === 'function' && prop !== 'constructor') { target.prototype[prop] = function(...args) { console.log(`[LOG] Class method "${prop}" called with args:`, args); const result = originalMethod.apply(this, args); console.log(`[LOG] Class method "${prop}" returned:`, result); return result; }; } } return target; } // Usage @logClass class Calculator { add(a, b) { return a + b; } multiply(a, b) { return a * b; } } const calc = new Calculator(); calc.add(5, 3); // Logs method call and resultManual Method Wrapping: For one-off cases, you could manually wrap individual methods, but this is not scalable for objects with many methods.
Final Recommendation
Go with the Proxy-based approach if you can support modern browsers—it’s the most flexible, handles both custom and built-in objects, and requires minimal code. The traditional decorator is a solid fallback for older environments or custom-only objects.
内容的提问来源于stack exchange,提问作者Ratcat71

