JavaScript系列(73)--装饰器详解

JavaScript装饰器详解

JavaScript装饰器是一个强大的语言特性,它让我们能够以声明式的方式修改类和类成员的行为。本文将深入探讨装饰器的原理、使用方法和最佳实践。

装饰器基础

小知识:装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 形式,其中 expression 必须计算为一个函数。

// 基础装饰器语法
function readonly(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

class Example {
    @readonly
    pi() { return 3.14; }
}

装饰器类型

1. 类装饰器

function logger(logPrefix) {
    return function(target) {
        // 保存原始方法
        const methods = Object.getOwnPropertyNames(target.prototype);
        
        methods.forEach(method => {
            if (method !== 'constructor') {
                const descriptor = Object.getOwnPropertyDescriptor(
                    target.prototype, 
                    method
                );
                
                const originalMethod = descriptor.value;
                descriptor.value = function(...args) {
                    console.log(`${logPrefix} Calling ${method}`);
                    const result = originalMethod.apply(this, args);
                    console.log(`${logPrefix} Finished ${method}`);
                    return result;
                };
                
                Object.defineProperty(target.prototype, method, descriptor);
            }
        });
    };
}

@logger('[UserService]')
class UserService {
    login() { /* ... */ }
    logout() { /* ... */ }
}

2. 方法装饰器

function measure(target, key, 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(`${key} took ${end - start}ms`);
        return result;
    };
    
    return descriptor;
}

class API {
    @measure
    async fetchData() {
        // 模拟API调用
        await new Promise(resolve => setTimeout(resolve, 1000));
        return { data: 'success' };
    }
}

3. 属性装饰器

function validate(validationFn) {
    return function(target, key) {
        let value = target[key];
        
        const getter = () => value;
        const setter = (newValue) => {
            if (!validationFn(newValue)) {
                throw new Error('Invalid value');
            }
            value = newValue;
        };
        
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    };
}

class User {
    @validate(value => value.length >= 3)
    name = '';
}

实际应用场景

1. 权限控制

function authorize(role) {
    return function(target, key, descriptor) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args) {
            if (!this.currentUser || this.currentUser.role !== role) {
                throw new Error('Unauthorized access');
            }
            return originalMethod.apply(this, args);
        };
        
        return descriptor;
    };
}

class AdminPanel {
    @authorize('admin')
    deleteUser(userId) {
        // 删除用户逻辑
    }
}

2. 性能优化

function memoize(target, key, descriptor) {
    const originalMethod = descriptor.value;
    const cache = new Map();
    
    descriptor.value = function(...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            return cache.get(key);
        }
        
        const result = originalMethod.apply(this, args);
        cache.set(key, result);
        return result;
    };
    
    return descriptor;
}

class MathUtils {
    @memoize
    fibonacci(n) {
        if (n <= 1) return n;
        return this.fibonacci(n - 1) + this.fibonacci(n - 2);
    }
}

3. 异步处理

function debounce(delay) {
    return function(target, key, descriptor) {
        const originalMethod = descriptor.value;
        let timeoutId;
        
        descriptor.value = function(...args) {
            clearTimeout(timeoutId);
            timeoutId = setTimeout(() => {
                originalMethod.apply(this, args);
            }, delay);
        };
        
        return descriptor;
    };
}

class SearchComponent {
    @debounce(300)
    search(query) {
        // 执行搜索逻辑
    }
}

最佳实践 ⭐

  1. 保持简单性

    • 装饰器应该只做一件事,并且做好这件事
    • 避免在装饰器中包含复杂的业务逻辑
  2. 错误处理

function errorBoundary(target, key, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args) {
        try {
            return await originalMethod.apply(this, args);
        } catch (error) {
            console.error(`Error in ${key}:`, error);
            // 可以添加错误报告逻辑
            throw error;
        }
    };
    
    return descriptor;
}
  1. 参数验证
function validateParams(...validators) {
    return function(target, key, descriptor) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args) {
            validators.forEach((validator, index) => {
                if (!validator(args[index])) {
                    throw new Error(`Invalid parameter at position ${index}`);
                }
            });
            return originalMethod.apply(this, args);
        };
        
        return descriptor;
    };
}

性能考虑 ⚡

  1. 避免过度使用

    • 每个装饰器都会增加一定的性能开销
    • 只在真正需要的地方使用装饰器
  2. 缓存装饰器结果

function cached(target, key, descriptor) {
    const originalMethod = descriptor.value;
    const cache = new WeakMap();
    
    descriptor.value = function(...args) {
        if (!cache.has(this)) {
            cache.set(this, new Map());
        }
        
        const methodCache = cache.get(this);
        const key = JSON.stringify(args);
        
        if (methodCache.has(key)) {
            return methodCache.get(key);
        }
        
        const result = originalMethod.apply(this, args);
        methodCache.set(key, result);
        return result;
    };
    
    return descriptor;
}

总结

装饰器是JavaScript中一个强大的特性,它能够:

  1. 增强代码的可读性和可维护性
  2. 实现横切关注点的分离
  3. 提供声明式的API
  4. 简化代码重用

学习建议:

  • 从简单的装饰器开始学习
  • 理解装饰器的工作原理
  • 在实际项目中谨慎使用
  • 注意性能影响
  • 保持代码的可维护性

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!

终身学习,共同成长。

咱们下一期见

你可能感兴趣的:(JavaScript,javascript,开发语言,ecmascript)