How not to lose the "this" of the current object

8

I've already done a similar question , however this time I have a somewhat more complex problem:

/ p>

ObjectTest1 = (function(){
  
  var init = function(){
      this.callback = null;
  }
  
  init.prototype = {
    setCallback : function(callback){
      this.callback = callback; // O CALLBACK DEFINIDO AQUI É init.prototype.methodTest2 PERTENCENTE AO ObjectTest2
    },
    applyCallback : function(){
      if(typeof this.callback == 'function'){
        this.callback();  // CHAMADA NORMAL DE init.prototype.methodTest2 DO OBJETO ObjectTest2
      }
    }
  }

  return init;
}());

ObjectTest2 = (function(){
  
  var init = function(){}

  init.prototype = {
    methodTest1 : function(){
      console.log('methodTest1');
    },
    methodTest2 : function(){
      console.log('methodTest2');
      var self = this; // AQUI this PASSA A SER ObjectTest1 MESMO EU NÃO TENDO USADO .call OU .apply
      if(typeof self.methodTest1 != 'undefined'){ // ESTE METODO NÃO EXISTE NO ObjectTest1 POIS ELE É DO ObjectTest2
        self.methodTest1();
      }else{
        console.log('ERROR');
      }
    }
  }
  var newInit = new init;
  return newInit;
}());

var o = new ObjectTest1();
o.setCallback(ObjectTest2.methodTest2);
o.applyCallback();

Doubt

  • Why did this change if I made a normal method call?
  • How can I make sure I do not lose the this reference of the object I am currently in?

Summary

I instantiate the ObjectTest1 and set it as callback a method of ObjectTest2 which in turn when executing should call another method of itself.

    
asked by anonymous 05.09.2016 / 21:11

4 answers

3

Well, I do not know yet why Javascript works like this, but I've had a lot of problems with that, when I try, for example, to simply pass the function reference as a callback.

I will show below one of the cases where I had this problem.

See:

var els = ['#id', '#id2'].map(document.querySelector);

The error returned is:

  

Uncaught TypeError: Illegal invocation (...)

It seems to be a context-related error where querySelector has been applied. That is, it was not invoked with the context of document , but of Array .

Using Bind

The solution was to use the bind method, which will set a scope for the last callback. Even if this callback goes into another scope, it will be executed in the context where it was passed in bind .

In the example case, the problem was solved like this:

 var els = ['#id', '#id2'].map(document.querySelector.bind(document))

Your case

In your case, I used bind when I pass callback to the ObjectTest1.setCallback function.

See:

ObjectTest1 = (function(){
  
  var init = function(){
      this.callback = null;
  }
  
  init.prototype = {
    setCallback : function(callback){
      this.callback = callback; // O CALLBACK DEFINIDO AQUI É init.prototype.methodTest2 PERTENCENTE AO ObjectTest2
    },
    applyCallback : function(){
      if(typeof this.callback == 'function'){
        this.callback();  // CHAMADA NORMAL DE init.prototype.methodTest2 DO OBJETO ObjectTest2
      }
    }
  }

  return init;
}());

ObjectTest2 = (function(){
  
  var init = function(){}

  init.prototype = {
    methodTest1 : function(){
      console.log('methodTest1');
    },
    methodTest2 : function(){
      console.log('methodTest2');
      var self = this; // AQUI this PASSA A SER ObjectTest1 MESMO EU NÃO TENDO USADO .call OU .apply
      if(typeof self.methodTest1 != 'undefined'){ // ESTE METODO NÃO EXISTE NO ObjectTest1 POIS ELE É DO ObjectTest2
        self.methodTest1();
      }else{
        console.log('ERROR');
      }
    }
  }
  var newInit = new init;
  return newInit;
}());

var o = new ObjectTest1();
o.setCallback(ObjectTest2.methodTest2.bind(ObjectTest2));
o.applyCallback();

Solution in jQuery

Only at the additional information level, in jQuery can you solve this problem using $.proxy .

Recommended reading:

05.09.2016 / 22:43
8

There's nothing unexpected about your code. this is correctly pointing to the context of the object that invoked the method where this was used .

Your stack is as follows:

new ObjectTest1()         <- contexto
.applyCallback()          <- função
[ObjectTest2.methodTest2] <- função
var self = this;          <- this: referência do contexto ObjectTest1()

ECMAScript 5 introduced the keyword bind () , which you can use when calling a function so that scope and body are preserved.

In your case, to preserve the context, preserve it with a reference (the reason that multiple JS implementations have a var self = this; line.)

Sources and more information:

05.09.2016 / 23:18
6

Rule: A function runs in the same context as the caller.

There are tools for modifying this. The new , as you used, but also .call(contexto) , .bind(contexto) and .apply(contexto) .

However in this case what fails is that you have var self = this; within the function. It's too late, because the context is already defined by the caller, ie Objeto1 .

In this case it seems to me that the simplest solution is to pass self = this to init , thus forcing the desired context when you create a new instance:

ObjectTest2 = (function() {
    var self;
    var init = function() {
        self = this;
    }

jsFiddle: link

    
05.09.2016 / 23:31
3

It may not be very practical, but this is always associated with a call , not a function declaration (they tried to improve this a little bit with functions with arrow = & gt ;, which follow other rules)

In your code

var o = new ObjectTest1();
o.setCallback(ObjectTest2.methodTest2); // Aqui você perdeu o this
o.applyCallback(); // Aqui você chamou (indiretamente) usando “o” como this

In the second line you have thrown out this, since the function does not know its this, it is set only in the call.

In the third line you used the object "o" as this, albeit indirectly.

Possibilities for this are:

  • method () → does not have this (I'm simplifying ...)
  • obj.method () → this is obj
  • new Method () → creates a new this.
  • .call () and .apply () → allow you to choose this dynamically
  • .bind () → allows you to set a this to a function so that it does not "forget" who it belongs to.
  • So, what is the solution? The simplest way in my opinion is to create a new function that does not need this:

    o.setCallback(function () {
        return ObjectTest2.methodTest2();
    });
    

    But using bind (see the other answers, use must be something like: ObjectTest2.methodTest2.bind(ObjectTest2) ) has the same effect. I've gotten used to doing it without a bind because some time ago not all browsers supported it.

        
    06.09.2016 / 00:00