1

I've seen a lot of discussion on this topic, and I've read through numerous articles, but I'm still confused about what this refers to in arrow functions.

I seem to be getting run-time errors associated with the following code (simplified):

export class Foo implements OnInit {
myProp: string;
myKeys: Array<any> = [];
mySubKeys: Array<any> = [];
@Input myObj;

. . . 

ngOnInit() {

this.myKeys = Object.keys(this.myObj); 
this.myKeys.forEach((key) => {
    this.myProp = key;
    this.mySubKeys = Object.keys(this.myObj[key]);
    this.mySubKeys.forEach((subkey) => { . . .  
. . . 

The error happens at this.myProp = key where the debugger complains that this is undefined.

My confusion is that for arrow functions I understood that this refers to the this preceding the scope in which the arrow function is called. In this case, wouldn't the preceding scope be the class scope, and therefore shouldn't this.myProp be defined?

I tried changing the arrow function to forEach(function(key) { . . . but got different errors.

Finally, if the this inside the arrow function doesn't refer to the class this then how do I refer to the class this and associated class properties (such as myProp) inside the arrow function?

Crowdpleasr
  • 3,574
  • 4
  • 21
  • 37
  • [fully explained here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) – Jaromanda X Apr 01 '19 at 05:58
  • 3
    Possible duplicate of [How does the "this" keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – zmag Apr 01 '19 at 05:59
  • could you add the errors, that you're receiving ? – k.s. Apr 01 '19 at 06:00
  • Possible duplicate of [Arrow Functions and This](https://stackoverflow.com/questions/28798330/arrow-functions-and-this) – adiga Apr 01 '19 at 06:12
  • Possible duplicate of [What does "this" refer to in arrow functions in ES6?](https://stackoverflow.com/questions/28371982/what-does-this-refer-to-in-arrow-functions-in-es6) – JJJ Apr 01 '19 at 07:38

3 Answers3

2

The reason for this is that the value of this in a function depends on how the function is called.

At it’s most basic level if the function is called as this.myKeys.forEach(), the value of this is the calling context which in this case is myKeys

In all cases however it isn’t going to be myKeys, so this.myProp & this.mySubKeys is not going to return value, it’s going to return undefined or raise an error.

This instability of this is an incredibly common problem in Javascript that has affected it since the early days.

In ES5 there are a number of methods we can use to stabilize the value of this. One common solution is to assign this to another variable at the top, usually called self, and then always use self in the function body, like so:

ngOnInit() {


   this.myKeys = Object.keys(this.myObj) {

    let self = this; // declare the variable to refer class object
    this.myKeys.forEach((key) => {
        self.myProp = key;
        self.mySubKeys = Object.keys(this.myObj[key]);
        self.mySubKeys.forEach((subkey) => { . . .  
   . . . 

Hope this will help!

TheParam
  • 10,113
  • 4
  • 40
  • 51
  • That worked! Many thanks! Now the question is, why does that work? I thought the arrow function was supposed to take its `this` from the enclosing context, which should the the `class` `this`, right? – Crowdpleasr Apr 01 '19 at 06:11
  • I have updated answer with explanation please check – TheParam Apr 01 '19 at 11:17
  • Thank you, TheParam. That's extremely helpful. The one last thing that's bothering me now is that another poster, chiril sarajiu, had a post (since taken down) that showed a Stackblitz replicating my code and showing that there was no issue. The `this` worked fine in his `forEach` block, without having to declare an extra `self` variable. So now I'm very confused. I'm starting to think there may be a problem with the Firefox debugger? – Crowdpleasr Apr 01 '19 at 18:46
  • To reinforce the issue, according to the "If a function is called on an object, such as in obj.myMethod() or the equivalent obj["myMethod"](), then ThisBinding is set to the object (obj in the example; §13.2.1)." . . . – Crowdpleasr Apr 01 '19 at 19:11
  • . . . This supports TheParam's explanation, as does the following... "In the case of the Array.prototype functions, the given callbackfn is called in an execution context where ThisBinding is set to thisArg if supplied; otherwise, to the global object." Given that forEach is an Array Prototype function, and I didn't supply a thisArg, this also bolsters TheParam's explanation. . . – Crowdpleasr Apr 01 '19 at 19:11
  • BUT . . . How to explain the fact that works? I won't be able to sleep until I understand this completely!! – Crowdpleasr Apr 01 '19 at 19:11
  • @Crowdpleasr This is a blatantly wrong answer and why you have accepted this is beyond me. For starters, `this.myKeys = Object.keys(this.myObj) {` is invalid syntax. Since arrow function is used in `foreach` callback, `this` outside and inside the callback refer to the same thing. `self` makes no difference. – adiga Apr 03 '19 at 13:13
  • @adiga - Many thanks for your feedback. The only reason I accepted the answer was because I made the suggested changes and then the code worked. However, now I'm having trouble reproducing the original issue, so that puts me back to square one, and I've unchecked the answer. – Crowdpleasr Apr 03 '19 at 17:58
  • . . . @adiga - Regarding the syntax, that was a mistake in editing, not in the original code, and I've since re-edited to clean it up. I'll leave "answer" unchecked until this matter is cleared up beyond doubt. The last remaining question I have now is that since `.forEach` is an `Array.prototype` function, which therefore has `Global` scope, doesn't the `forEach` method change the scope of the arrow function to `Global` rather than the enclosing class object? If so might this be a source of the problems I encountered when `this` was undefined? (Or am I misinterpreting how forEach works?) – Crowdpleasr Apr 03 '19 at 18:00
  • @adiga - I was able to reproduce the issue (I believe). Please download this Stackblitz to your local browser, open the console/debugger after the form has rendered, then click on the `"+"` button in the "Email" section. In the debugger, you'll see that initially `this` is defined, but once you've entered the `forEach` loop by stepping (F11) to line 79, `this` is no long defined. Using TheParam's method, `let self = this` and changing the `forEach` code accordingly with `self` replacing `this` solves the problem. – Crowdpleasr Apr 05 '19 at 19:50
  • @adiga - Apologies in advance that I had to upload the whole project, rather than provide a simplified version. I tried to reproduce the issue from the bottom up, but so far have been unable. Please also forgive the somewhat rough formatting, as I'm just trying to make the code work and haven't got to the final formatting stage yet. Many thanks in advance if you can shed light as to why `this` suddenly becomes undefined inside the `forEach` loop. I'm sure there's an obvious explanation, but I haven't been able to find it yet. – Crowdpleasr Apr 05 '19 at 19:54
  • @TheParam - Please note my comments above, tks – Crowdpleasr Apr 05 '19 at 19:55
  • @Daniel Piñeiro - Please note my comments above, tks – Crowdpleasr Apr 05 '19 at 19:55
  • . . . Also, as expected, using `forEach(callbackFn,this)` syntax works as does @TheParam solution. But the question, is why are either of those two approaches necessary if indeed the `this` is taken from the enclosing context, and that fact is demonstrated by @Daniel Piñeiro and @chiril sarajiu 's earlier Stackblitz examples. – Crowdpleasr Apr 05 '19 at 21:16
0

The this in the code you expose should work as you intend to. I think your problem comes from this line:

this.myKeys = Object.keys(this.myObj) {

There is an opening { when it should be just a close ; .

this.myKeys = Object.keys(this.myObj);

The code should look like:

ngOnInit() {
    this.myKeys = Object.keys(this.myObj);
    this.myKeys.forEach((key) => {
        this.myProp = key;
        this.mySubKeys = Object.keys(this.myObj[key]);

Edit:

Adding demo to try the this is working: https://stackblitz.com/edit/stackoverflow-mykeys-demo

Note that the Input() has to have the () too.

Daniel Piñeiro
  • 3,009
  • 1
  • 16
  • 26
  • Many thanks, Daniel. That's a very good catch. Unfortunately, I mistakenly added the extra "}" in the code above when I edited the original code to make it simpler for posting purposes. The original code that had problems did not have the extra "}". That was my fault for poor editing, so sorry for any confusion. As of now, it seems TheParam's explanation is correct (because his solution fixed the problem), but several others indicate I shouldn't have had the problem in the first place and that something else must be wrong in my code, so I'd be very grateful to get to the heart of the mystery! – Crowdpleasr Apr 01 '19 at 18:30
  • 1
    If you can post your code in any way maybe we can help you a little more. Maybe it happens because how this.myObj is declared or something like that. I gonna upload an example to show you how that should work fine and going to edit my answer. – Daniel Piñeiro Apr 02 '19 at 07:04
  • Many thanks, Daniel. That's very helpful. Regarding my code, as it's fairly complicated, it may take me a while to clean it up, so thank you in advance for your patience, and thank you also for your offer to look at it. Also, somewhat perplexingly, I changed the code these last few days and now I can't replicate the issue I had earlier! . . . But regarding your Stackbltiz example . . . – Crowdpleasr Apr 02 '19 at 20:52
  • . . . One question: I changed the last `console.log` in `hello.component.ts` to `console.log("I'm working: " + this.allTheSubKeys[subkey]);` and it evaluates to `undefined`, but I also added a `console.log(this.allTheProps[key])` after ` this.allTheProps.push(key);` and that one works! Any idea why one works but the other doesn't? – Crowdpleasr Apr 02 '19 at 21:01
  • 1
    Yes. Refering to the first question: `console.log("I'm working: " + this.allTheSubKeys[subkey]);` this.allTheSubKeys it's a matrix of string, so if you want to get a value you have to use a index. (You can change the foreach to add a index: `this.mySubKeys.forEach((subkey,x) => { console.log("I'm working: " + this.allTheSubKeys[x]); });` – Daniel Piñeiro Apr 03 '19 at 06:15
  • 1
    Refering to the `this.allTheProps`, if you see in the example, it can be two things: array of indexes or array of object keys. In the case of array indexes it works as you want. As the other solution, if you add the index in the foreach it would help you. – Daniel Piñeiro Apr 03 '19 at 06:27
  • Thank you, and much appreciated! Separately, if you have a minute, do you mind taking a look at ? Why is the ViewChildren query returning a zero length result? This is another post I made (), which is driving me nuts (but I haven't gotten many responses). Thank you in advance, if convenient! – Crowdpleasr Apr 03 '19 at 06:36
-2

From MDN :

An arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules. So while searching for this which is not present in current scope they end up finding this from its enclosing scope.