装饰器允许在不增加代码复杂性的情况下向类和属性添加新功能。
JS实现装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function cachingDecorator(fn){ let cache = new Map(); return function (x) { if(cache.has(x)){ console.log(`fn${x}的结果已经缓存了`); return cache.get(x); }else { let result = fn(x) cache.set(x, result); return result; } } } const squire = x => x ** 2 const cachingSquire = cachingDecorator(squire)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function cachingDecorator(fn){ let cache = new Map(); return function (...args) { let key = hash(args) if(cache.has(key)){ console.log(`fn${args}的结果已经缓存了`); return cache.get(key); }else { let result = fn.apply(this, args) cache.set(key, result); return result; } } } const show1 = (...args)=> { console.log(args); return args } const show2 = cachingDecorator(show1)
|
1 2 3 4 5 6 7
| function delayDecorator(fn, ms){ return function (...args) { console.log(`我是装饰后的函数,延时了${ms / 1000}秒`); setTimeout(() => fn.apply(this, args), ms) } }
|
1 2 3 4 5 6 7 8
| function debounceDecorator(fn, ms){ let timeout; return function (...args) { clearTimeout(timeout) timeout = setTimeout(() => fn.apply(this, args), ms) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function throttleDecorator(fn, ms){ let isThrottled = false, savedArgs, savedThis; function wrapper(...args){ if(isThrottled){ savedArgs = args savedThis = this return } fn.apply(this, args) isThrottled = true setTimeout(() => { isThrottled = false if(savedArgs){ wrapper.apply(savedThis, savedArgs) savedArgs = savedThis = null } }, ms) } return wrapper }
|
尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中(stage 2 proposal)。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。
装饰器有一种特殊的语法。它们以 @
符号为前缀,放置在我们需要装饰的代码之前。另外,可以一次使用多个装饰器。
类装饰器
类装饰器应用于整个类。因此,我们所做的任何修改都会影响整个类。对类装饰器所做的任何事情都需要通过返回一个新的构造函数来替换类构造函数。
1 2 3 4 5 6 7 8 9 10 11 12
| function addTimestamp(target) { console.log(target) target.prototype.timestamp = new Date() }
@addTimestamp class MyClass {}
const instance = new MyClass() console.log(instance.timestamp)
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| function addTimestamp(options) { return function(target) { target.prototype.timestamp = options?.initialDate || new Date(); } }
@addTimestamp({ initialDate: new Date('2024-01-01') }) class MyClass {}
const instance = new MyClass(); console.log(instance.timestamp);
|
属性装饰器
最常见的就是设置属性只读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function readOnly(prototype, propertyKey, descriptor) { console.log(prototype); console.log(propertyKey); console.log(descriptor); descriptor.writable = false; }
class Car { @readOnly make = 'Toyota'; }
const myCar = new Car(); console.log(myCar.make); try { myCar.make = 'Honda'; } catch(error) { console.log(error.message); } console.log(myCar.make);
|
方法装饰器
注意:修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
普通方法装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function logMethod(prototype, propertyKey, descriptor) { console.log(prototype); console.log(propertyKey); console.log(descriptor);
const originalMethod = descriptor.value;
descriptor.value = function (...args) { console.log(`调用方法${propertyKey},参数为:`, args); return originalMethod.apply(this, args); }; return descriptor; }
class MyClass { @logMethod greet(name) { return `Hello, ${name}!`; } }
const instance = new MyClass(); console.log(instance.greet('World'));
|
访问器装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| function capitalize(prototype, propertyKey, descriptor) { console.log(prototype); console.log(propertyKey); console.log(descriptor);
const originalGetter = descriptor.get; descriptor.get = function () { const result = originalGetter.call(this); return result.toUpperCase(); }; return descriptor; }
class Person { constructor(name) { this._name = name; } @capitalize get name() { return this._name; } }
const person = new Person('john'); console.log(person.name);
|
参数装饰器
暂时还未支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function logParameter(prototype, propertyKey, parameterIndex) { const originalMethod = prototype[propertyKey]; prototype[propertyKey] = function (...args) { console.log(`方法${propertyKey}的第${parameterIndex}个参数值为:`, args[parameterIndex]); return originalMethod.apply(this, args); }; }
class User { greet(@logParameter name) { return `Hello, ${name}!`; } }
const user = new User(); console.log(user.greet('Alice'));
|