This keyword in Fat arrow functions

This is a continuaiton of my previous post where I attempted to explain how this behaves in javascript. In this post I will continue to explore how this keyword works with fat arrow style functions (=>) which are available in ES6.

Lets start with a primer on this:

1. this refers to window if accessed globally. But in strict mode, in global mode it would be undefined.

(function(){
  this.name = "yo yo honey singh";
})();
console.log(window.name == this.name);
(function(){
  "use strict";
  this.name = "yo yo honey singh"; // ERROR as this is undefined
})();

2. this refers to an instance if in constructor mode

function Food(n){
  this.name = n;
}
var f = new Food("chicken makhni");
console.log(f.name); //chicken makhni

3. this refers to an object if in a method inside an object.

var player1 = {
  name: "sandeep arneja",
  printName: function(){
    console.log("player's name is: " + this.name);
  }
}
player1.printName(); // player's name is: sandeep arneja

4. this refers to DOMElement when used as an event listener

$(document.body).on("click", function(){
  console.log(this); // Body DOMElement
})

5. this refers to an object when it is bounded to it via bind, call or apply

  window.name = "yo yo honey singh"
  var sayHello= function(){
    console.log("hello " + this.name);
  }
  var sayHello2 = sayHello.bind({name: "Pankaj Udas"})
  sayHello(); // yo yo honey singh
  sayHello2(); // Pankaj Udas

Scope-By-Flow

In Javascript the this keyword is resolved as scope-by-flow. I borrowed this term of scope-by-flow from a blog post at jsrocks. What i mean to say is that the value of this can change depending upon how the function which has this is being called.

window.name = "bananas";

var player1 = {
  name: "sandeep arneja",
  printName: function(){
    console.log("player's name is: " + this.name);
  }
}
player1.printName(); // player's name is: sandeep arneja

window.setTimeout(player1.printName ,1000); // player's name is: bananas

In the examplea above the flow has changed. Now when player1.printName is called, this refers to the global window.

window.name = "bananas";

var callback = function(cb){
  cb();
}

var player1 = {
  name: "sandeep arneja",
  printName: function(){
    console.log("player's name is: " + this.name);
  }
}
player1.printName(); // player's name is: sandeep arneja

callback(player1.printName); // player's name is: bananas

In the example above again the flow has changed, the printName function is no longer called on the object, meaining its not like object, followed by a dot, followed by function.

Similar effects come with the bind, call & apply functions. They change the value of this even when the function is a method on an object and this is supposed to be pointing to the object.

window.name = "global window name";
var a = {
  name: "yo yo honey singh",
  print: function(){
    console.log(this.name);
  }
}
a.print(); // yo yo honey singh
x = a.print;
x(); // global window name

Above is another example of how the value of this changed depending upon how it was called. Here this is resolved based upon on how it is called. When calling a method, like object . methodName, then the value of this in the function is equal to the object which appers before the dot.

Function Scope & Lexical Scoping

Lets cover two more concepts and then we will be ready to dive in to how this changes with Fat Arrows (=>) in ES6.

1. Function Scope: Any variable which is defined inside a function, is visible inside the entire function. This is different from block scope.

var a = function(){
  if(true){
    var b = 5;
  }
  b = b + 9;
  console.log(b);
}
a(); // 14
(function(){
  b = 5
  console.log(b); // 5
  var b;
  c = 5;
  console.log(c); // 5
})();
console.log(c); // 5
console.log(b); // Uncaught ReferenceError: b is not defined

2. Lexical Scoping: The scope of the inner function includes the scope of the outter function.

var outter = function(){
  var name = "outter";
  var age = 100;

  var inner = function(){
    var name = "inner";

    var print = function(){
      console.log("name is : " + name + " and age is: " + age);
    }

    print();
  }

  inner();
}

outter(); // name is inner and age is 100

The inner function includes the scope of the outter function, even when after the outter function has returned.

var setIt = function(){
   var x = "yo yo honey singh";

   var printer = function(){
    console.log(x);
   }

   return printer;
}

var p = setIt();
p(); // yo yo honey singh
p(); //yo yo honey singh

In the example above thanks to Lexical Scoping the printer function’s scope includes the scope of setIt and is therefore able to resolve variable x. Note its able to do so even after the outter function, setIt has been called and ended execution.

Lexical function plays a key role in resolving variables when in nested functions.

Reference:

  • http://pierrespring.com/2010/05/11/function-scope-and-lexical-scoping/

Fat Arrow and This

A function defined with fat arrow syntax or style resolved this lexically only. The this keyword no longer has the special properties mentioned above. Lets take an example:

window.name = "global name";

var x = {
   name: "yo",
   print: function(){
     console.log(this.name);
   },
   print2: () => {
     console.log(this.name);
   }
}

x.print(); // yo
x.print2(); // global name

x.print.call({name: ":)"}); // yo
x.print2.call({name: ":)"}); // global name

In the example above when the function print2 is called, it resolves this lexically, just like any other variable. this is never the object on which the method was called or bound to. It is what is in scope, which includes the outter functions scope. In this case since there was no outter function, this goes to default value of window or it would be undefined if we were in strict mode.

Lets take another example:

var students = {
  records: [],
  add: function(name){
    this.records.push(name);
  },
  addAll: function(names){
    names.map( (n) => this.add(n));
  }
}

students.addAll(["yo", "jo", "mo", "po"]);
console.log(students.records);

In the example above, we used a fat arrow function as a callback to the map function inside addAll. In the callback function, this keyword was lexically scoped and therefore took the value of this from its parent function. Its parent function is addAll where this resolved to students.

Here is how we would go about doing it without a fat arrow:

var students = {
  records: [],
  add: function(name){
    this.records.push(name);
  },
  addAll: function(names){
    names.map(function(n){
      this.add(n);
    });
  }
}

students.addAll(["yo", "jo", "mo", "po"]); // this.add is not a function
console.log(students.records);

So this does not resolve to students in the callaback of map. This makes sense as we know this is resolved with a scopy-by-flow principle. So we need to pass in the this to this function. Lets use lexical scoping to help us do so.

var students = {
  records: [],
  add: function(name){
    this.records.push(name);
  },
  addAll: function(names){
    var s = this;
    names.map(function(n){
      s.add(n);
    });
  }
}
students.addAll(["yo", "jo", "mo", "po"]); // this.add is not a function
console.log(students.records);

Now in the example above, the callback function us using variable s over this. The AddAll function which is the parent function of the callback function has stored the value of this in a regular variable s. The callback function of map has a scope which includes its parent functions scope which is AddAll and that function has the variable s which holds the value of this. Now when the callback function runs it is able to resolve s and get the value of this`.

Note what i have done above could also be achieved by passing this to the map function as it supports passing in a value for this and also by binding the callback function to this.

window.name = "global window name";
var a = {
  name: "yo yo honey singh",
  print: function(){
    console.log(this.name);
  },
  print2: () => {
    console.log(this.name);
  }
}
a.print(); // yo yo honey singh
x = a.print;
x(); // global window name
a.print2(); // global window name
x = a.print2;
x(); // global window name

Reference:

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
  • http://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/

Conclusion:

In Fat-Arrow Syntax this is lexically scoped, meaning the value of this is what its defined around it. In other words the value of this is inherited from the enclosing/parent scope.