Ente Ente - 1 month ago 10
Node.js Question

Set this for required arrow-functions

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(...)


Answer

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.