1

I'm trying to set this in various scenarios.

The following code executed in node.js v6.8.1 will print what is commented at the end of each line:

function requireFromString(src) {
  var Module = module.constructor;
  var m = new Module();
  m._compile(src, __filename);
  return m.exports;
}

(function(){
  var f1 = (() => {console.log(this.a === 1)});
  var f2 = function(){ console.log(this.a === 1) };
  var f3 = requireFromString('module.exports = (() => {console.log(this.a === 1)});');
  var f4 = requireFromString('module.exports = function(){ console.log(this.a === 1) };');

  console.log('Body:');
  console.log(this.a === 1);                          // true
  (()=>console.log(this.a === 1))();                  // true
  (()=>console.log(this.a === 1)).call(this);         // true
  (function(){console.log(this.a === 1)})();          // false
  (function(){console.log(this.a === 1)}).call(this); // true


  console.log('\nFunctions:');
  f1();                                               // true
  f1.call(this);                                      // true
  f2();                                               // false
  f2.call(this);                                      // true
  f3();                                               // false [1]
  f3.call(this);                                      // false [2]
  f4();                                               // false 
  f4.call(this);                                      // true

}).apply({a:1});

With the documentation for this and arrow functions I can explain all cases except the ones labelled with [1] and [2].

Can somebody shed some light on the observed behaviour? And maybe give a hint how I can call f3 so that the function prints true.


Notes

  • The requireFromString-function is adapted from Load node.js module from string in memory and is just a hack to slim down this stackoverflow question. In practice this is replaced by an ordinary require(...)
Ente
  • 2,301
  • 1
  • 16
  • 34
  • 2
    The point of an arrow function is to inherit the value of `this` from the lexical context. If that's not what you want, then don't use an arrow function. If you want to "set" or "control" the value of `this`, then use a regular function call and one of the various methods available (`.apply()`, `.call()`, `obj.method()`, etc...) for controlling the value of `this` in that function call. – jfriend00 Oct 19 '16 at 15:29
  • Do you have the expcted behaviour when you call the `f4();` and the `f4.call(this);`? – Stavros Zavrakas Oct 19 '16 at 15:30
  • to give you some context: I'm refactoring [FluentFlow](https://www.npmjs.com/package/fluentflow) which uses user-supplied functions, so called 'matchers'. As [detecting arrow functions seems to be hard](https://stackoverflow.com/questions/28222228/javascript-es6-test-for-arrow-function-built-in-function-regular-function) (and I don't like the accepted answer) I want to set `this` for the 'matcher' no matter what kind of function is provided. – Ente Oct 19 '16 at 15:54
  • even more context: [this is how a matcher should be defiend by the user](https://github.com/Enteee/FluentFlow/blob/kill-deasync/test/matcherTest.js#L10) and [this is how the matcher is called by FluentFlow](https://github.com/Enteee/FluentFlow/blob/kill-deasync/core/matcher.js#L79), yes: i do know that i could simply pass obj as a parameter. But I do like the readability of the `this` version. – Ente Oct 19 '16 at 16:33

1 Answers1

4

The reason is because "fat arrow functions" always take their this lexically, from the surrounding code. They cannot have their this changed with call, bind, etc. Run this code as an example:

var object = {
  stuff: 'face',

  append: function() {
    return (suffix) => {
      console.log(this.stuff + ' '+suffix);
    }
  }
}
var object2 = {
  stuff: 'foot'
};

object.append()(' and such');
object.append().call(object2, ' and such');

You will only see face and such.

So, as far as why that doesn't work in the case of f3, it's because it's a self-contained module being required. Therefore, it's base-level arrow functions will only use the this in the module, they cannot be bound with bind, call, etc etc as discussed. In order to use call on them, they must be regular functions, not arrow functions.


What does "lexical this" mean? It basically works the same as a closure. Take this code for example:

fileA.js:

(function () {
    var info = 'im here!';

    var infoPrintingFunctionA = function() {
        console.log(info);
    };

    var infoPrintingFunctionB = require('./fileB');

    infoPrintingFunctionA();
    infoPrintingFunctionB();
})();

fileB.js:

module.exports = function() {
    console.log(info);
};

What will be the result? An error, info is not defined. Why? Because the accessible variables (the scope) of a function only includes the variables that are available where the function is defined. Therefore, infoPrintingFunctionA has access to info because info exists in the scope where infoPrintingFunctionA is defined.

However, even though infoPrintingFunctionB is being called in the same scope, it was not defined in the same scope. Therefore, it cannot access variables from the calling scope.

But this all has to do with the variables and closures; what about this and arrow functions?

The this of arrow functions works the same as the closure of other variables in functions. Basically, an arrow function is just saying to include this in the closure that is created. And in the same way you couldn't expect the variables of fileA to be accessible to the functions of fileB, you can't expect the this of the calling module (fileA) to be able to be referenced from the body of the called module (fileB).

TLDR: How do we define "surrounding code", in the expression "lexical this is taken from the surrounding code?" The surrounding code is the scope where the function is defined, not necessarily where it is called.

aaronofleonard
  • 2,546
  • 17
  • 23
  • can you please further elaborate "So, as far as why that doesn't work in the case of f3, it's because it's a self-contained module being required." ? Why is `this` in this case not taken from the surrounding code? – Ente Oct 19 '16 at 16:16
  • @Ente I updated the answer with some additional clarification, let me know if it helps. – aaronofleonard Oct 19 '16 at 16:43
  • thank you `this` helps ;) So I'm back to [detecting arrow functions](https://stackoverflow.com/questions/28222228/javascript-es6-test-for-arrow-function-built-in-function-regular-function) and raising exceptions if one is supplied. – Ente Oct 19 '16 at 17:40