2

Just made the switch over to ES6, running io.js.

I'm writing some class code, but I'm having an unexpected error.

'use strict';

var _ = require('lodash');

class Country {

  constructor(blocked) {
    this.blocked = ['USA'];
  }

  ok(input) {
    console.log('Receiving...',input['country']);
    console.log('Blocked:', this.blocked);
    if(_.includes('USA', input['country'])) {
      return -1;
    }
    return 0;
  }

}

module.exports = Country;

For whatever reason, everything works well except for the this.blocked class variable.

When I console log it, it shows up as Blocked: undefined.

Any thoughts as to what's going on here?

Addition

I'm calling the function in another class as follows...

var Country  = require('./filters/country.js');
var country  = new Country();

class FilterClassifier {

    constructor() {
      var self = this;
      self.filters = [country.ok];
    }

    userFilter(params) {

      var self = this;

      var input = {
        country   : params.country,
      };

      console.log(self.filters[0](input));
    }

}

module.exports = FilterClassifier;
David K.
  • 679
  • 2
  • 10
  • 23
  • How exactly are you using the "ok()" function? Are you passing it as an event handler or something? – Pointy Jul 09 '15 at 17:58
  • What if you do `var c = new Country(); c.ok([]);` - does it work then? – Pointy Jul 09 '15 at 17:59
  • I'm calling it from a script. I don't think it should matter in this case though; since it's within the same class it should have access to the class variables (denoted by `this.var_name`). – David K. Jul 09 '15 at 18:00
  • 2
    That depends on whether the value of `this` is a reference to the instance or not, and *that* depends on how "ok()" is invoked. JavaScript does not work the way other languages work. The expression `this.blocked` isn't guaranteed to work. – Pointy Jul 09 '15 at 18:00
  • Good point, Pointy! I've just added to the post how it's invoked. – David K. Jul 09 '15 at 18:02

2 Answers2

4

As mentioned in the comments, the way you are calling the function removed the context of the function.

self.filters = [country.ok];

and then

console.log(self.filters[0](input));

means that this inside ok will not be country. You'll need to do

self.filters = [country.ok.bind(country)];

or

self.filters = [() => country.ok()];

I'd recommend reading up on this in javascript. The short answer in this particular case is that this is defined based on how a function is called.

var a = {};
a.fn = function(){};

var b = {};
b.fn = a.fn;

b.fn();

When calling b.fn(), this inside the function is b. This is because when calling a function using the form foo.bar(), this inside a function is defined as the object that the function is called on (foo). In your case, you have

self.filters[0]();

that means that this inside your ok functions is actually self.filters for the same reason.

If you have a specific this that matters, it is your responsibility to make sure that as you pass around a function, that the function you are passing will set the proper this. Using fn.bind(foo) will return a new function which, when called, call fn with a given this.

loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • This worked! Would you be willing to share a bit more about why this happens, and how I can avoid it in the future? – David K. Jul 09 '15 at 18:14
  • @DavidK. Expanded my answer a bit. – loganfsmyth Jul 09 '15 at 18:20
  • Great answer, Logan. Thanks for your help! Understanding that `this` is contextual in Javascript is hugely important for more complex applications. – David K. Jul 09 '15 at 18:22
  • BTW, if you're using already ES6, you can use [fat arrow functions(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) to bind `this` to its lexical scope and avoid these issues. – mAAdhaTTah Jul 09 '15 at 18:29
0

It looks like this.blocked is scoped to the constructor function, this in ok doesn't have a identifier called blocked.

Edit: I was wrong, thanks Pointy. In the interest of helping others who stumble on this post I'm not going to delete my wrong answer but instead share what I learned. David, in the comments you asked for a way to fix it. this could be replaced with let variables to avoid confusion or use this with bind() when a function is called.

class Country {

  // variable scoped to class with let
  // set blocked = value in constructor
  // should not need 'this.' nor 'bind()'

  let blocked = [];

... 
}

"Determining this" says you had default this binding: undefined. You hoped for implicit binding and got explicit binding to work with country.ok.bind(country).

Further reading illustrating how the fat arrow => and var self = this; make this confusing.

Community
  • 1
  • 1
Dylan Valade
  • 5,565
  • 6
  • 42
  • 56