I'm using the extremely useful local fat arrow to preserve this
context in callbacks. However, sometimes I need to access the value that this
would've had if I hadn't used the fat arrow.
One example are event callbacks, where this
has the value of the element that the event happened on (I'm aware that in this particular example you could use event.currentTarget
, but lets assume you can't for the sake of an example):
function callback() {
// How to access the button that was clicked?
}
$('.button').click(() => { callback() });
Note: I've e across this question which deals with this exact same issue, but in CoffeeScript.
I'm using the extremely useful local fat arrow to preserve this
context in callbacks. However, sometimes I need to access the value that this
would've had if I hadn't used the fat arrow.
One example are event callbacks, where this
has the value of the element that the event happened on (I'm aware that in this particular example you could use event.currentTarget
, but lets assume you can't for the sake of an example):
function callback() {
// How to access the button that was clicked?
}
$('.button').click(() => { callback() });
Note: I've e across this question which deals with this exact same issue, but in CoffeeScript.
Share Improve this question edited May 23, 2017 at 11:55 CommunityBot 11 silver badge asked Aug 15, 2015 at 13:45 fstanisfstanis 5,5641 gold badge25 silver badges45 bronze badges 3-
2
Use a normal function, and store the outer
this
in a variable. – Oriol Commented Aug 15, 2015 at 13:57 - "...and lexically binds the this value" developer.mozilla/es/docs/Web/JavaScript/Reference/… – a Commented Aug 15, 2015 at 16:47
-
Don't forget about good ol'
event.currentTarget
to get the event target regardless of thethis
binding (note that it will be anHTMLElement
, not a jQuery object). – Jared Smith Commented Aug 15, 2015 at 21:36
3 Answers
Reset to default 3You could write a decorator function that wraps a fat-arrow function inside another function which allows the access to the usual this
and passes that value to the fat-arrow function as an additional argument:
function thisAsThat (callback) {
return function () {
return callback.apply(null, [this].concat(arguments));
}
}
So when you call thisAsThat
with a fat-arrow function, this basically returns a different callback function that, when called, calls the fat-arrow function with all the arguments but adds this
as an argument in the front. Since you cannot bind fat-arrow functions, you can just call bind
and apply
on it without having to worry about losing the value.
You can then use it like this:
element.addEventListener('click', thisAsThat((that, evt) => console.log(this, that, evt)));
This will log the this
of the current scope (as per fat-arrow rules), the this
of the callback function as that
(pointing to the element for event handlers), and the event itself (but basically, all arguments are still passed on).
You don't need to use arrow functions for that purpose.
You can simply use the Function's bind
to change the function's scope:
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./ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class='button'>Hello, world!</button>
<span></span>
For other plex scenarios where you want to use both arrow functions and call other functions in the same context, you can use Function's call
or apply
:
// let's suppose your callback function expects a Date and a Number param
$('.button').click(() => callback.call(this, new Date(), 23));
// or
$('.button').click(() => callback.apply(this, [new Date(), 23]));
@poke's answer is the right idea, but there are a couple issues:
function thisAsThat (callback) {
return function () {
return callback.apply(null, [this].concat(arguments));
}
}
First, arguments
is not a true array, so calling concat()
as shown above will result in a nested array:
[0] = this
[1] = [ [0] = param1, [1] = param2, ... ]
To fix this, use slice() to convert arguments
to a true array:
return callback.apply(null, [this].concat(Array.prototype.slice.call(arguments)));
Result:
[0] = this
[1] = param1
[2] = param2
...
Second, passing null
for the first parameter to apply() didn't work for me; I had to pass class Foo's this
explicitly. Here's a plete example:
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)));
}
}
}
With this approach, the onCheckboxClick()
callback has access to both the class this
and the checkbox element (as the first parameter) that would have been this
in a typical click
event callback. The downside to using captureThis()
is that you lose TypeScript's type safety for the callback, so Typescript can't stop you from messing up the callback function signature.