Prateek Prateek - 1 month ago 6
Javascript Question

Difference in binding of functions calls?

I have an object "me" with functions defined for it.

me.prototype.f = function(args){
console.log("f called");
};


I need to provide f as a callback in an async function.

me.prototype.operation = function (){
var self = this;
var client; // an io client
//client.on("data", self.f); --a
//client.on("data", self.f.bind(self)) --b
}


Is there any difference between a & b above in this case and is there any situation where a) might fail?

In which scenarios do we get a problem without binding? Generally it is when values might change based on the context. (eg. closure using loop index value)

So, what can be the different scenarios where values might act differently.




From the answer I arrived at the following conclusions. Please confirm the same :

a) The scope will change as the function is passed as reference and will pick up the local reference unless the context is not bound using bind, call or apply.

b) Specially in callbacks/async functions, if the context is changed (eg. setTimeout, the context will change, hence the meaning of self will change during call.)

c) It has no relation to closure, since in closure the Lexical Environment is checked for evaluating values, however, here the context during the time of execution is evaluated

Answer

self.f is a reference to the f function.

self.f.bind(self) is a new function which when called invokes f with self as this.

Is there any difference between a & b above in this case and is there any situation where a) might fail?

Yes. The behaviour depends on how you make the call:

function me() {}
me.prototype.f = function() {
  console.log(this);
};
me.prototype.operation = function() {
  client.on("this.f", this.f);
  client.on("this.f.bind(this)", this.f.bind(this));
};

var client = {
  on: function(whatever, callback) {
    var self = this;
    setTimeout(function() {
      console.log("=== " + whatever + " ===");
      self.f = callback;

      // `this` not specified
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback();

      // `this` belongs to current function (passed to setTimeout)
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback.call(this);

      // `self` is a reference to the client object
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      callback.call(self);

      // `this` is determined by the calling object (client again)
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      self.f();

    }, 1000);
  }
};


(new me()).operation();

Update

I've decided to add more to the answer, as you've added more to the question. I am sorry that it turned out this long.

How thisworks

The value of this is determined on function call, not on function definition.

  • When calling a function which is not a property of an object this will be undefined in strict, or refer to the global object in non-strict mode.

    function f1() {
      console.log(this === window);
    }
    function f2() {
      'use strict';
      console.log(this === undefined);
    }
    
    f1();  // true
    f2();  // true
    
  • When calling a function which is a property of an object, this will refer to the that object.

    var object = {
      f: function() {
        console.log(this === object);
      }
    };
    object.f();   // true
    
  • When calling a function with the new prefix, this will refer to the newly created object.

    var backup;
    function Example() {
      backup = this;
      this.value = 0;
    }
    
    var example = new Example();
    console.log(example === backup);  // true
    console.log(example.value);       // 0
    
  • When calling a function which is a not a property of an object, but is a part of the object's prototype chain, this will refer to the calling object.

    function Example() {}
    Example.prototype.f = function() {
      console.log(this === example);
    };
    
    var example = new Example();
    example.f();   // true
    

Changing this

Mentioned behaviour is often not what we want. Consider the following situation.

    function Counter() {
      this.count;
      this.limit;
      this.interval;
    }
    Counter.prototype.countTo = function(number) {
      this.count = 1;
      this.limit = number;
      this.interval = setInterval(update, 1000);

      function update() {
        console.log(this.count);
        if (++this.count > this.limit)
          clearInterval(this.interval);
      }
    };

    var counter = new Counter();
    counter.countTo(5);   // undefined, NaN, NaN, NaN, NaN, NaN...

this gets its value once the function is called, not when it's defined. The interval function will be called at a later time, and this will refer to the global object. In most cases, that is not what one's after.

Changing this implicitly

The historical way to solve the setInterval issue is to assign a reference of this to a variable visible by the problematic function, and use that variable instead.

function Counter() {}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var self = this;

  function update() {
    console.log(self.count);
    if (++self.count > self.limit)
      clearInterval(self.interval);
  }
  this.interval = setInterval(update, 1000);
};

var counter = new Counter();
counter.countTo(5);

The same using a closure, to prevent pollution of function scope.

function Counter() {}

Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  this.interval = setInterval((function(self) {
    return function() {
      console.log(self.count);
      if (++self.count > self.limit)
        clearInterval(self.interval);
    };
  }(this)), 1000);
};

var counter = new Counter();
counter.countTo(5);


Creating a new update function whenever a Counter is created is not the most efficient way to do it. Let's say we want to move it to the prototype.

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(this.update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...

The problem now is getting update to know about selfs, since they are no longer defined in the same scope, and there's only one update function for all Counters.

We can create a mediator function that will enclose the value (copy a reference) of this and use it to make the call.

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var update = (function(self) {
    return function() {
      self.update();
    };
  }(this));

  this.interval = setInterval(update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);

We can use arrow functions, which bind this lexically, that is, its value is determined on function definition, regardless of how or when it's called.

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(() => this.update(), 1000); // neat.
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);

Changing this explicitly

Let's say our update function is defined elsewhere, and we wish to use it instead of our prototype function.

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update, 1000);
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...

Using the previously mentioned approaches, we could attach a reference to update within the object or its prototype, and use, say, an arrow function to bind this. If we don't like that sort of overhead, we have three powerful functions at our disposal for explicitly setting this:

Calling bind on a function results in a new function whose this is set to the first parameter.

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update.bind(this), 1000); // not bad.
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5);

Using call or apply on a function will make the call with this set to the first parameter.

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  var self = this;
  this.interval = setInterval(function() {
    update.call(self);
  }, 1000);
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5);