9

I'm going through the JavaScript30 challenge, and in lesson 3 he's got some event listener calling a function that references the element it's called on as this:

const inputs = document.querySelectorAll('.controls input');
function handleUpdate() {
  const suffix = this.dataset.sizing || '';
  document.documentElement.style.setProperty(`--${this.name}`, this.value + suffix);
}
inputs.forEach(input => input.addEventListener('change', handleUpdate));
inputs.forEach(input => input.addEventListener('mousemove', handleUpdate));

I'm trying to rewrite it with ES6 arrow function, but I can't get the this to work right. I got a workaround using target:

const handleUpdate = (e) => {
  const that = e.target;
  const newValue = `${that.value}${that.dataset.sizing || ''}`;
  etc.
}

but I first tried to bind the function like that:

input.addEventListener('change', handleUpdate.bind(this));

But this inside the function still points to window and I don't understand why.

Is there no "right" way to bind the function to the element in this case?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Yann
  • 604
  • 2
  • 7
  • 16
  • If you want to use `this` then you need to use regular functions. Arrow functions don't really work properly with `this`. – slebetman Jan 28 '17 at 20:47
  • 1
    Also, is there any right way to do this with arrow functions? **No** – slebetman Jan 28 '17 at 20:47
  • I think I recall reading something that said, or suggested, that Arrow functions were written expressly to avoid affecting the 'this'. – David Thomas Jan 28 '17 at 20:52
  • 1
    Either a normal function and `this` or `e.currentTarget` (not `e.target`!!) – Oriol Jan 28 '17 at 21:08
  • Arrow functions are only allowed to inherit the value of 'this' from the environment they were defined in, rather than the context they were invoked in. The demo code uses a regular function, and 'this' references the environment in which it's invoked. That isn't possible with arrow functions by definition, hence why they can't be used anywhere you need to use 'this'. – Rich Aug 09 '21 at 16:14

1 Answers1

10

What is this?

this is a special keyword in Javascript that refers to the executing environment of the function:

  • If you execute a function in the global scope, this will be bound to the window
  • If you pass the function to a callback for an event handler, this will be bound to the DOM element that raised the event

Binding

The bind method basically says, when you call the function, replace this with whatever my argument is. So, for example:

let a = {}
function test_this() {
     return this === a;
}  

test_this(); // false
test_this.bind(a)(); // true (.bind() returns the bound function so we need to call the bound function to see the result)

Additionally arrow functions are simply syntactic sugar for binding the function's this to the current value of this. For example,

let b = () => { /* stuff */ }

is the same as

let b = (function () { /* stuff */}).bind(this);

(basically, I know this is an oversimplication)

Your predicament

In the normal course of events (not using arrow functions), this is bound to the DOM element.

When you're executing the creation of the event handler input.addEventListener('change', handleUpdate.bind(this)); you're running in the global scope (so this === window). So you're effectively running input.addEventListener('change', handleUpdate.bind(window)); (which is the behavior you're noticing). And using the arrow function is the same thing.

If you want to replace the callback with an anonymous function you should instead do:

const handleUpdate = function (e) {
  const that = e.target;
  const newValue = `${that.value}${that.dataset.sizing || ''}`;
  // etc.
}
Jeremy Wiebe
  • 3,894
  • 22
  • 31
Tomas Reimers
  • 3,234
  • 4
  • 24
  • 37
  • 4
    Arrow functions have no concept of `this`. `this` inside of an arrow function is whatever `this` is in their containing lexical environment. –  Jan 28 '17 at 21:03
  • 1
    Hence the "I know this is an oversimplification". I think trying to discuss lexical scoping when someone is just learning JS is probably not the best pedagogical method. If you're reading this and want the more correct explanation: arrow functions don't bind `this` so `this` simply refers to whatever it referred to in the enclosing lexical scope. However that is functionally the same to if they had just bound this to the enclosing scope. – Tomas Reimers Jan 28 '17 at 21:20
  • 1
    The point is that your entire answer can be replaced with "Arrow functions have no concept of this. this inside of an arrow function is whatever this is in their containing lexical environment. Don't use arrow functions if you need to bind the value of `this`." and possibly some references to the ECMAScript 2015 Language Specification. –  Jan 28 '17 at 21:22
  • That would fail to explain why his attempt with .bind didn't work – Tomas Reimers Jan 28 '17 at 21:24
  • It most definitely would explain why his attempt to bind didn't work "Arrow functions have no concept of this. this inside of an arrow function is whatever this is in their containing lexical environment. Don't use arrow functions if you need to bind the value of this." –  Jan 28 '17 at 21:25
  • I'm referring to `input.addEventListener('change', handleUpdate.bind(this));` – Tomas Reimers Jan 28 '17 at 21:26
  • And again, the reason that bind did not work there is because "Arrow functions have no concept of this. this inside of an arrow function is whatever this is in their containing lexical environment. Don't use arrow functions if you need to bind the value of this." –  Jan 28 '17 at 21:27
  • My apologies, I think there is a misunderstanding -- I don't believe that he/her is trying to .bind(this), I think they're wondering why this in the function doesn't correspond to the DOM element they expect it should... – Tomas Reimers Jan 28 '17 at 21:30
  • No, the code in the question would definitely work as expected if they were to not use arrow functions. The reason that this in the function does not correspond to the DOM element the expect it should is because "Arrow functions have no concept of this. this inside of an arrow function is whatever this is in their containing lexical environment. Don't use arrow functions if you need to bind the value of this." –  Jan 28 '17 at 21:32
  • doing `input.addEventListener('change', (function () {/*stuff*/}).bind(this));` would not work, regardless of if they're using arrow functions – Tomas Reimers Jan 28 '17 at 21:33
  • `inputs.forEach(function(input) { input.addEventListener('change', handleUpdate.bind(this)) });` would most definitely have worked if `handleUpdate` had not been defined using an arrow function. –  Jan 28 '17 at 21:36
  • I agree fully. But their question explicitly asks about the .Bind case at the bottom – Tomas Reimers Jan 28 '17 at 21:37
  • Which is obviously extracted from the `inputs.forEach` function call. Regardless, the whole problem could be solved by simply not using arrow functions when `this` is important. You're making this far more complicated than it should be. The long and short of it is... well I've already stated the long and short of it multiple times. I'm not going to repeat myself again. Good day. –  Jan 28 '17 at 21:40