8

I have following code:

let myObj = {
  foo: "bar",
  getFoo: function() {
    console.log(this.foo);
  },
  method: function() {
    if (true) {
      window.addEventListener('scroll', this.getFoo);
    } else {
      window.removeEventListener('scroll', this.getFoo);
    }
  }
}

window.addEventListener('click', () => {
  myObj.method();
});

It returns undefinded, since (for reasons unknown to me) this refers to the window object if getFoo is called as a callback in an addEventListener function. Now if I used an arrow function inside myObj.method -

window.addEventListener('scroll', () => {
  this.getFoo();
});

This would work, but then I called an anonymous function and can't do removeEventListener later. How could I get this working with a non-anonymous function?

galingong
  • 323
  • 1
  • 4
  • 14
  • You have to write `myObj.getFoo` instead of `this.getFoo` The same goes for `this.foo` – Blauharley Apr 20 '18 at 10:51
  • 1
    `window.addEventListener('scroll', this.getFoo.bind(this))`. – Estus Flask Apr 20 '18 at 11:01
  • Thank you for the reference, it was really useful to read about using object methods as callback functions. Also you are right, according to the reference you linked, the only way to do what I'd like using a non-anonymous function is `.bind`. – galingong Apr 20 '18 at 15:43
  • 1
    If you want to be able to use `removeEventListener` to remove the handler, you need to pass exactly the same value to it that you passed to `addEventListener`. I.e. if you use `.bind`, then you need to store the return value somewhere somehow. – Felix Kling Apr 21 '18 at 00:16

4 Answers4

10

From MDN:

An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

Mamun
  • 66,969
  • 9
  • 47
  • 59
2

You could use call and bind to call a method or function in the context of a certain object so that this refers to the context of myObj:

let myObj = {
  foo: "bar",
  getFoo: function() {
    console.log(this.foo);
  },
  method: function() {
    if (true) {
      // here you have to use bind to create a function in a certain context
      window.addEventListener('scroll', this.getFoo.bind(this));
    } else {
      // here is the same
      window.removeEventListener('scroll', this.getFoo.bind(this));
    }
  }
}

window.addEventListener('click', () => {
  // create and call method in the context of myObj or any other object that is passed as first parameter of call
  myObj.method.call(myObj);
  // or because this in the function method refers to myObj at the beginning, just call method with obj
  myObj.method();
});
Blauharley
  • 4,186
  • 6
  • 28
  • 47
  • Thanks, but still returns undefined for me. Should this really work? Have you tried it? – galingong Apr 20 '18 at 11:13
  • 1
    You are welcome, sorry I only tested part of it. Now it should work. – Blauharley Apr 20 '18 at 12:48
  • Yeah, now it works, but the key was using `.bind`. With that, it works even without `.call(myObj)`, simply by calling `myObj.method()` – galingong Apr 20 '18 at 15:07
  • Assuming `if (true)` is actually containing a variable of some sorts, then removing the event handler won't work correctly. `.bind` always returns a new function, whereas you need to pass the exact same function object to `removeEventListener` that you passed to `addEventListener`. – Felix Kling Apr 21 '18 at 00:11
  • 1
    `myObj.method.call(myObj);` is unnecessary btw. It's 100% the same as `myObj.method()`. – Felix Kling Apr 21 '18 at 00:14
0

I would advise you to take a quick look here, if you want to know how the "this" keyword actually works...

A solution for your problem might be to create a variable called selfObj (or something similar), then using that - to access the properties of myObj.

Edit: This version is tested and works. Not sure if it is simple enough anymore though... The key here is pass the exactly same parameters to both addEventListener and removeEventListener. For that reason I have created the scrollEventListener array attribute for the myObj object. Then using destructuring pass that as parameters...

Hope this helps...

'use strict';

    let myObj = {
        foo: 'bar',
        counter: 0,
        getFoo: function() {
            const selfObj = this;
            console.log(selfObj.foo);
        },
        scrollEventListener: [
            'scroll',
            function () {
                myObj.getFoo();
            },
            true
        ],
        method: function() {
            const selfObj = this;
            selfObj.counter++;
            if (selfObj.counter % 2 === 0) {
                window.addEventListener(...selfObj.scrollEventListener);
            } else {
                window.removeEventListener(...selfObj.scrollEventListener);
            }
        }
    };

    window.addEventListener('click', () => {
        myObj.method();
    });
Bilger Yahov
  • 377
  • 4
  • 14
  • 1
    Have you tested it? It's not working for me for some reason. – galingong Apr 20 '18 at 11:12
  • I am so sorry! Here's the problem: `window.addEventListener('scroll', function () { selfObj.getFoo(); });` – Bilger Yahov Apr 20 '18 at 12:13
  • So, basically you call the `selfObj.getFoo()` from inside an anonymous function, that you pass as a callback to the `addEventListener`. Hope that helps! – Bilger Yahov Apr 20 '18 at 12:17
  • Let me know how that goes... – Bilger Yahov Apr 20 '18 at 12:18
  • Still, `selfObj` is going to be the `window` object, when `getFoo` is called as a callback function. The only way I've managed to get it working is by `bind`ing `this` to the callback for `addEventListener`. – galingong Apr 20 '18 at 15:46
  • Have you tried it? Copy and Paste the code that I've written above, change the last thing that I mentioned and then you should get "bar"... I've tried it and it works. – Bilger Yahov Apr 20 '18 at 20:43
  • 1
    Yes, you're right, but then again - I won't be able to do `removeEventListener` on an anonymous function. – galingong Apr 21 '18 at 00:10
  • 1
    You should update your answer with the correct code. Because as it is it won't work. Also doing `const selfObj = this;` inside `getFoo` is unnecessary. – Felix Kling Apr 21 '18 at 00:13
  • Hi! Thanks for the remarks! I have updated my answer. Now it should both work for adding the event listener, as well as - for removing it. However, I am not sure in the simplicity of the answer anymore... Hope that helps now. Good luck! – Bilger Yahov Apr 22 '18 at 08:34
-1

If there is a this in anonymous function, this is points to window, not to object where you define it. than you use function , this is binds to object where you define this function.

Егор Лебедев
  • 1,161
  • 1
  • 10
  • 26
  • *"this is binds to object where you define this function."* No, the value of `this` is determine by how a function is **called** not *when* or *how* it is defined (except for arrow functions). Whether a function us anonymous or not has not impact on `this`. – Felix Kling Apr 21 '18 at 00:14