TypeScript: Как использовать как жирную стрелку, так и это?

Я использую чрезвычайно полезную локальную жирную стрелку, чтобы сохранить this контекст в обратных вызовах. Однако иногда мне нужно получить доступ к значению, которое this имело бы, если бы я не использовал жирную стрелу.

Одним из примеров являются обратные вызовы событий, где this имеет значение элемента, в котором произошло событие (я знаю, что в этом конкретном примере вы можете использовать event.currentTarget, но предположим, что вы не можете для примера):

function callback() {
    // How to access the button that was clicked?
}

$('.button').click(() => { callback() });

Примечание. Я столкнулся с этим вопросом, который касается именно этой же проблемы, но в CoffeeScript.

Ответ 1

Вы можете написать функцию декоратора, которая обертывает функцию fat-arrow внутри другой функции, которая позволяет получить доступ к обычному this и передает это значение функции fat-arrow в качестве дополнительного аргумента:

function thisAsThat (callback) {
    return function () {
        return callback.apply(null, [this].concat(arguments));
    }
}

Поэтому, когда вы вызываете thisAsThat с thisAsThat fat-arrow, это в основном возвращает другую функцию обратного вызова, которая при вызове вызывает функцию fat-arrow со всеми аргументами, но добавляет this как аргумент в начале. Поскольку вы не можете связывать функции стрелки, вы можете просто вызвать bind и apply на нем, не беспокоясь о потере значения.

Затем вы можете использовать его следующим образом:

element.addEventListener('click', thisAsThat((that, evt) => console.log(this, that, evt)));

Это регистрирует this из текущей области (в соответствии с правилами жира стрелки), то this функции обратного вызова в that, that (указывающем на элемент для обработчиков событий), а сам (но в основном, все аргументы по - прежнему передаются на событие).

Ответ 2

Для этой цели вам не нужно использовать функции стрелок.

Вы можете просто использовать bind Function для изменения области действия:

function callback() {
  // How to access the button that was clicked?
  $("span").text(this.text());
}

var b = $('.button'); // in Typescript you should be using let instead of var
b.click(callback.bind(b));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class='button'>Hello, world!</button>
<span></span>

Ответ 3

Ответ @poke - это правильная идея, но есть пара вопросов:

function thisAsThat (callback) {
    return function () {
        return callback.apply(null, [this].concat(arguments));
    }
}

Во-первых, arguments не являются истинным массивом, поэтому вызов concat() как показано выше, приведет к вложенному массиву:

[0] = this
[1] = [ [0] = param1, [1] = param2, ... ]

Чтобы исправить это, используйте slice() для преобразования arguments в истинный массив:

        return callback.apply(null, [this].concat(Array.prototype.slice.call(arguments)));

Результат:

[0] = this
[1] = param1
[2] = param2
...

Во-вторых, передача null для первого параметра apply() не работает для меня; Я должен был пройти класс Foo this явно. Вот полный пример:

class Foo {

    public setClickHandler(checkbox: JQuery): void {
        checkbox.click(this.captureThis(this.onCheckboxClick));
    }

    protected onCheckboxClick(checkbox: HTMLInputElement, event: Event) {
        // 'this' refers to class Foo
    }

    protected captureThis(callback) {
        var self = this;
        return function () {
            return callback.apply(self, [this].concat(Array.prototype.slice.call(arguments)));
        }
    }

}

При таком подходе обратный вызов onCheckboxClick() имеет доступ как к классу this и к элементу checkbox (в качестве первого параметра), который был бы this в типичном onCheckboxClick() события click. Недостатком использования captureThis() является то, что вы теряете безопасность типа TypeScript для обратного вызова, поэтому TypScript не может помешать вам испортить подпись функции обратного вызова.