Как избежать жесткого кодирования этого? в декораторах

Я прочитал Как реализовать декоратор typescript? и несколько источников, но есть что-то, что я и не мог сделать с декораторами.

class FooBar {
    public foo(arg): void { 
        console.log(this);
        this.bar(arg);
    }
    private bar(arg) : void { 
        console.log(this, "bar", arg);
    }
}

Если мы вызываем функцию foo:

var foobar = new FooBar();
foobar.foo("test"); 

Объект FooBar записывается в консоль на console.log(this); в foo

Строка "FooBar {foo: function, bar: function} bar test" записывается в консоль на console.log(this, "bar", arg); в bar.

Теперь позвольте использовать декоратор:

function log(target: Function, key: string, value: any) {
    return {
        value: (...args: any[]) => {
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args); // How to avoid hard coded this?
            var r = JSON.stringify(result);
            console.log(`Call: ${key}(${a}) => ${r}`);
            return result;
        }
    };
}

Мы используем ту же функцию, но оформлены:

class FooBar {
    @log
    public foo(arg): void { 
        console.log(this);
        this.bar(arg);
    }
    @log
    private bar(arg) : void { 
        console.log(this, "bar", arg);
    }
}

И мы вызываем foo, как мы это делали раньше:

var foobarFoo = new FooBar();
foobarFooBar.foo("test");

Объект Window записывается в консоль на console.log(this); в foo

И bar никогда не вызывается foo, потому что this.bar(arg); вызывает Uncaught TypeError: this.bar is not a function.

Проблема заключается в жестко закодированном this внутри декоратора log:

value.value.apply(this, args);

Как сохранить исходное значение this?

Ответ 1

Не используйте функцию стрелки. Используйте выражение функции:

function log(target: Object, key: string, value: any) {
    return {
        value: function(...args: any[]) {
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args);
            var r = JSON.stringify(result);
            console.log(`Call: ${key}(${a}) => ${r}`);
            return result;
        }
    };
}

Таким образом он будет использовать контекст функции this вместо значения this при вызове журнала.


Кстати, я бы рекомендовал отредактировать параметр дескриптор/значение и вернуть его вместо того, чтобы перезаписать его, вернув новый дескриптор. Таким образом, вы сохраняете свойства в настоящее время в дескрипторе и не будете перезаписывать то, что другой декоратор мог бы сделать с дескриптором:

function log(target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) {
    var originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
        var a = args.map(a => JSON.stringify(a)).join();
        var result = originalMethod.apply(this, args);
        var r = JSON.stringify(result);
        console.log(`Call: ${key}(${a}) => ${r}`);
        return result;
    };

    return descriptor;
}

Подробнее в этом ответе - См. пример "Плохой и хороший" в разделе "Пример - без аргументов > Примечания"

Ответ 2

Я считаю, что вы можете использовать

var self = this;

чтобы сохранить 'this' в этой конкретной точке. Затем просто используйте self в более поздней точке, где вы хотели бы, чтобы этот this