Accessing this in a forEach loop results in undefined
Asked Answered
C

3

29

I'm iterating through an array using forEach in one of my Class's methods. I need access to the instance of the class inside the forEach but this is undefined.

var aGlobalVar = {};

(function () {

    "use strict";

  aGlobalVar.thing = function() {
      this.value = "thing";   
  }

  aGlobalVar.thing.prototype.amethod = function() {
      data.forEach(function(d) {
      console.log(d);
      console.log(this.value);
  });
}
})();

var rr = new aGlobalVar.thing();
rr.amethod(); 

I have a fiddle I'm working on here: http://jsfiddle.net/NhdDS/1/ .

Conservatoire answered 18/10, 2013 at 9:0 Comment(3)
Well I do have local variables in the full script. This is just a stripped back snippet for the example.Conservatoire
Sure, just seemed a bit odd.Eccrinology
@Qantas94Heavy: Also lets him make the stuff inside strict.Halfway
H
62

In strict mode if you call a function not through a property reference and without specifying what this should be, it's undefined.

forEach (spec | MDN) allows you to say what this should be, it's the (optional) second argument you pass it:

aGlobalVar.thing.prototype.amethod = function() {
  data.forEach(function(d) {
    console.log(d);
    console.log(this.value);
  }, this);
  // ^^^^
}

Alternately, arrow functions were added to JavaScript in 2015. Since arrows close over this, we could use one for this:

aGlobalVar.thing.prototype.amethod = function() {
  data.forEach(d => {
    console.log(d);
    console.log(this.value);
  });
}
Halfway answered 18/10, 2013 at 9:3 Comment(6)
Didn't know about this! Absolutely clearer than storing this in a temporary var and use in foreach context. Thanks for sharing!Milne
I'm going with this. I'm building an app on Chromium so ECMA5 is fine in this instance. ThanksConservatoire
I don't suppose there is a way in TypeScript to tell the IDE the type of this when inside the forEach loop? I'm currently doing the following const self = <NotifServer>this then using self there after to get the autocomplete from my IDE - not idealParquet
@ForceHero: Good question. I don't know, and a quick search here on SO didn't turn up any existing question. You might do a more thorough search than I did, and scour the TypeScript spec, and if it hasn't already been asked and you don't find the answer, post a question about it.Halfway
Thanks @T.J.Crowder . I actually ended up using arrow functions which allow you to access this in the context outside of the callback - as described by Qantas 94 Heavy's answer belowParquet
Give this guy a medal! I like this. ThanksMeditate
E
6

Since you're using strict mode, when a function is called that isn't a property of an object, this will have the value undefined by default (not the global object). You should store its value manually:

var aGlobalVar = {};

(function () {
    "use strict";

    aGlobalVar.thing = function () {
        this.value = "thing";   
    };

    aGlobalVar.thing.prototype.amethod = function () {
        var self = this;
        data.forEach(function (element) {
          console.log(element);
          console.log(self.value);
        });
    };
})();

var rr = new aGlobalVar.thing();
rr.amethod();

Nowadays, with ES2015 you can also use arrow functions, which uses the this value of the outside function:

function foo() {
  let bar = (a, b) => {
    return this;
  };

  return bar();
}

foo.call(Math); // Math

T.J. Crowder's solution of using the second argument of forEach also works nicely if you don't like the idea of the temporary variable (ES5 code: works in pretty much any browser these days, except IE8-).

Eccrinology answered 18/10, 2013 at 9:3 Comment(2)
Thanks. I knew this. I have no idea why I didn't use it! Btw should that be console.log(thing.value); in the code above?Conservatoire
@MikeRifgin: haha, yes.Eccrinology
M
1

What I had to to is add this in the every forEach that I was using (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach ). Binding in the constructor is not needed, as I am using arrow functions. So now my code is:

resetPressed = () => {
    this.transport_options.forEach(function (transport_option) {
        this.pressed_percentages.forEach(function (percentage) {
            filters[transport_option][percentage] = false;
        }, this)
    }, this);

    filters.isFilterActive = false;
    this.setState({
        filtersState: filters,
        opacity: filters.isFilterActive ? 1 : 0.5
    });

}


<TouchableHighlight
    underlayColor={'transparent'}
    onPress={this.resetPressed}
    style={styles.iconView}>
Milburt answered 27/1, 2020 at 20:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.