Saturday 13 July 2019

Arrow Functions and binding

When doing some tests with Jasmine I wanted to spy on one method invoked from the constructor (I mean method and constructor of the same class). As you need to set the spy before having the instance created, you'll have to spy on the class method (so you'll end up spying on each instance of the class, not just a particular one), not on the instance method. As you know, JavaScript classes are syntax sugar on top of the prototypes mechanism. Apart from "recording" the call, I did not want to invoke the original method, but a function assigning some mock data to one property of the object. So I was setting my spy like this

spyOn(MyClass.prototype, "myMethod").and.callFake(() => this.items = mockData.items);

let myInstance = new MyClass();
console.log(myInstance.items); //undefined!

I had read that the function provided to callFake would receive as this the object on which we are spying, but the above code was not setting the property. Can you spot the problem? Well, it's simple, we should use a function expression rather than an arrow function:

spyOn(MyClass.prototype, "myMethod").and.fake(function(){
 this.items = mockData.items;
});

Other than its cool syntax Arrow functions trap the lexical this rather than using a dynamic this. This is very useful for event handlers, callbacks... and has made the typical "self-closure" pattern disappear, I mean:

self = this;
myButton.addEventListener("click", function(){
 self.myMethod();
});

//has given place to:
myButton.addEventListener("click", () => this.myMethod());

An arrow function binds to the function the this that exists at the moment of creating the function. The arrow function gets bound to that this similar to when you call function.prototype.bind. Once you have a bound function, calling bind on it again will not have a real effect. You get a new function wrapping the bound one, but the invokation will call the internal function with the first bound this. I guess function.prototype.bind creates a wrapper that uses function.prototype.call, and function.prototype.call is useless with bound functions:


class Person{
 constructor(name){
  this.name = name;
 }
 
 sayHi(){
  return "Hi, I'm " + this.name;
 }
}

let f1 = Person.prototype.sayHi.bind({name: "Francois"});
console.log(f1());

console.log(f1.call({name: "Chloe"}));

let f2 = f1.bind({name: "Chloe"});
console.log(f2());

//Hi, I'm Francois
//Hi, I'm Francois
//Hi, I'm Francois

I assume Jasmine is using using Function.prototype.bind to provide the this to the function invoked from callFake, so an arrow won't make it.

Notice that both functions created with Function.prototype.bind and arrow functions do not have a .prototype property (obviously they still have a [[Prototype]] property as every JavaScript object) and can not be used as a constructor with new (you'll get an "is not a constructor" error)

No comments:

Post a Comment