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

Asked

Viewed 187 times

8

I had already made one similar question, however this time I am with a problem a little more complex :

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

  • Because the this was changed if I made a normal method call?
  • How can I not miss the reference this of the object I am currently?

Summary

I instate the ObjectTest1 and I set as callback a method of ObjectTest2 which in turn when being executed should call another method of itself.

4 answers

8

There’s nothing unexpected in your code. this is pointing, correctly, 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 keyword bind(), that 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 several JS implementations have a line var self = this;.)

Sources and more information:

6

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

There are tools to modify this. The new, like you used, but also .call(contexto), .bind(contexto) and .apply(contexto).

However in this case what fails is that you have var self = this; in the function. It is too late, because then the context is already defined by the one who called, ie the Objeto1.

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

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

jsFiddle: https://jsfiddle.net/0o5wqgpz/

  • 1

    It really is a valid answer and it revolves my problem perfectly, however I believe it is more applicable the answer of @Wallace, because if I want to change to return init; each instance would have to have its own scope of self.

3


Well, I don’t know yet why Javascript works like this, but I’ve had a lot of problems with it, when I try to, for example, simply pass the reference function as callback.

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

Behold:

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

The error returned is:

Uncaught Typeerror: Illegal Invocation(...)

Seems to be an error related to the context in which the querySelector was applied. That is, it was not invoked in the context of document, and yes of Array.

Use of the Bind

The solution was to use the method bind, which will define a scope for the past callback. Even if that callback enters another scope, it will be executed in the context it was passed to bind.

In the case of the example, the problem was solved thus:

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

Your case

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

Behold:

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 level of additional information, in jQuery it is possible to solve this problem using $.proxy.

Recommended reading:

3

It may not be very practical, but this is always associated with a calling for, not to a function statement (they tried to improve it a little with the arrow functions =>, 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 row you threw out this, since the function does not know your this, it is just set in call.

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

The possibilities for this are:

  1. method() does not have this (I’m simplifying...)
  2. obj.method() this is obj
  3. new Method() creates a new this.
  4. .call() and . apply() allow you to choose this dynamically
  5. .bind() lets you fix this to a function so it doesn’t "forget" who it belongs to.

So what’s the solution? The simplest way in my opinion is to create a new function that doesn’t need this:

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

But use the bind (see the other answers, the use should be something like: ObjectTest2.methodTest2.bind(ObjectTest2)) has the same effect. I got used to doing without bind because a while ago not all browsers supported.

  • Interesting to see that this scope also works. + 1.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.