起因
在ts中,装饰器是一种对class中属性处理的方式,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function format() { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const targetValue = descriptor.value; descriptor.value = (...args: any[]) => { console.log('format', ...args) targetValue.call(...args); }; }; }
class Foo { @format() public log(log: string){ console.log('foo log', log) } }
|
装饰器
这种做法可以处理成员函数的一些执行处理,上述做法可以在执行 Foo.log 时获取到输入的参数,并对参数进行处理,可以更好地处理函数中的一些全局操作,例如日志记录之类
但是在前端编写代码时,非常容易碰到一些this指向问题,因为在js中,function需要指定this或者例如箭头函数,没有this,但是在一些情况下,又必需使用箭头函数来进行获取所在函数的上下文this
这样就会存在问题,就是在使用装饰器对class中的箭头函数进行处理时,无法达到理想效果,但是改成普通函数时又会获取不到this
因为在class中的箭头函数是被当初属性申明的,装饰器无法获取 PropertyDescriptor.value
,所以不能当作普通函数进行处理,例如以下写法,会在 output
中无法获取this的指向导致报错,但是使用箭头函数的写法
。例如outputArrow
函数,又无法让format
生效
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 format() { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const targetValue = descriptor.value; descriptor.value = (...args: any[]) => { console.log('format', ...args) targetValue.call(...args); }; }; }
class Foo { public log(log: string){ console.log('log', log) } @format() public output(data: string){ this.log(data); }
@format() public outputArrow = (data: string) => { this.log(data) } }
const foo = new Foo();
foo.output('log');
foo.outputArrow('log');
|
解决方法
在PropertyDescriptor
中,我们可以改写PropertyDescriptor.get
属性来进行属性劫持,因为在获取get的值的时候,当前函数的this即为目标对象本身,所以获取的this一定是对的
改写所有需要装饰的方法为普通函数,不使用箭头函数
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 34 35 36 37 38 39 40 41
| function format() { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const targetValue = descriptor.value; function createFn(callTarget: any,...args: any[]) { console.log('format', ...args); callTarget.call(...args); };
return { configurable: true, get() { const bindTarget = targetValue.bind(this); return (...args: any[]) => createFn.bind(this)(bindTarget, ...args); }, } }; }
class Foo { public log(log: string){ console.log('log', log) } @format() public output(data: string){ this.log(data); }
@format() public outputArrow(data: string) { this.log(data) } }
const foo = new Foo();
foo.output('log');
foo.outputArrow('log');
|