Preserving a reference to "this" in JavaScript prototype functions

68,869

Solution 1

For preserving the context, the bind method is really useful, it's now part of the recently released ECMAScript 5th Edition Specification, the implementation of this function is simple (only 8 lines long):

// The .bind method from Prototype.js 
if (!Function.prototype.bind) { // check if native implementation available
  Function.prototype.bind = function(){ 
    var fn = this, args = Array.prototype.slice.call(arguments),
        object = args.shift(); 
    return function(){ 
      return fn.apply(object, 
        args.concat(Array.prototype.slice.call(arguments))); 
    }; 
  };
}

And you could use it, in your example like this:

MyClass.prototype.myfunc = function() {

  this.element.click((function() {
    // ...
  }).bind(this));
};

Another example:

var obj = {
  test: 'obj test',
  fx: function() {
    alert(this.test + '\n' + Array.prototype.slice.call(arguments).join());
  }
};

var test = "Global test";
var fx1 = obj.fx;
var fx2 = obj.fx.bind(obj, 1, 2, 3);

fx1(1,2);
fx2(4, 5);

In this second example we can observe more about the behavior of bind.

It basically generates a new function, that will be the responsible of calling our function, preserving the function context (this value), that is defined as the first argument of bind.

The rest of the arguments are simply passed to our function.

Note in this example that the function fx1, is invoked without any object context (obj.method() ), just as a simple function call, in this type of invokation, the this keyword inside will refer to the Global object, it will alert "global test".

Now, the fx2 is the new function that the bind method generated, it will call our function preserving the context and correctly passing the arguments, it will alert "obj test 1, 2, 3, 4, 5" because we invoked it adding the two additionally arguments, it already had binded the first three.

Solution 2

For your last MyClass example, you could do this:

var myThis=this;
this.elements.each(function() { myThis.doSomething.apply(myThis, arguments); });

In the function that is passed to each, this refers to a jQuery object, as you already know. If inside that function you get the doSomething function from myThis, and then call the apply method on that function with the arguments array (see the apply function and the arguments variable), then this will be set to myThis in doSomething.

Solution 3

I realize this is an old thread, but I have a solution that is much more elegant, and has few drawbacks apart from the fact that it is not generally done, as I have noticed.

Consider the following:

var f=function(){
    var context=this;
}    

f.prototype.test=function(){
    return context;
}    

var fn=new f();
fn.test(); 
// should return undefined because the prototype definition
// took place outside the scope where 'context' is available

In the function above we defined a local variable (context). We then added a prototypical function (test) that returns the local variable. As you have probably predicted, when we create an instance of this function and then execute the test method, it does not return the local variable because when we defined the prototypical function as a member to our main function, it was outside the scope where the local variable is defined. This is a general problem with creating functions and then adding prototypes to it - you cannot access anything that was created in the scope of the main function.

To create methods that are within the scope of the local variable, we need to directly define them as members of the function and get rid of the prototypical reference:

var f=function(){
    var context=this;    

    this.test=function(){
        console.log(context);
        return context;
    };
}    

var fn=new(f);
fn.test(); 
//should return an object that correctly references 'this'
//in the context of that function;    

fn.test().test().test(); 
//proving that 'this' is the correct reference;

You may be worried that because the methods are not being created prototypically, different instances may not really be data-separated. To demonstrate that they are, consider this:

var f=function(val){
    var self=this;
    this.chain=function(){
        return self;
    };    

    this.checkval=function(){
        return val;
    };
}    

var fn1=new f('first value');
var fn2=new f('second value');    

fn1.checkval();
fn1.chain().chain().checkval();
// returns 'first value' indicating that not only does the initiated value remain untouched,
// one can use the internally stored context reference rigorously without losing sight of local variables.     

fn2.checkval();
fn2.chain().chain().checkval();
// the fact that this set of tests returns 'second value'
// proves that they are really referencing separate instances

Another way to use this method is to create singletons. More often than not, our javascript functions are not being instantiated more than once. If you know that you will never need a second instance of the same function, then there is a shorthand way to create them. Be warned, however: lint will complain that it is a weird construction, and question your use of the keyword 'new':

fn=new function(val){
    var self=this;
    this.chain=function(){
        return self;
    };        

    this.checkval=function(){
        return val;
    };  
}    

fn.checkval();
fn.chain().chain().checkval();

Pro's: The benefits to using this method to create function objects are plentiful.

  • It makes your code easier to read, since it indents the methods of a function object in a way that makes it visually easier to follow.
  • It allows access to the locally defined variables only in methods originally defined in this manner even if you later add prototypical functions or even member functions to the function-object, it cannot access the local variables and whatever functionality or data you store on that level remains safe and inaccessible from anywhere else.
  • It allows a simple and straight-forward way to define singletons.
  • It allows you to store a reference to 'this' and maintain that reference indefinitely.

Con's: There are some drawbacks to using this method. I don't pretend to be comprehensive :)

  • Because the methods are defined as members to the object and not prototypes - inheritance can be achieved using member definition but not prototypical definitions. This is actually incorrect. The same prototypical inheritance can be achieved by acting on f.constructor.prototype.

Solution 4

You can set the scope by using the call() and apply() functions

Solution 5

You can create a reference to the this object or you can use the with (this) method. The later is extremely useful when your using event handlers and you have no way of passing in a reference.

MyClass = function() {
    // More code here ...
}

MyClass.prototype.myfunc = function() {       
    // Create a reference
    var obj = this;
    this.element.click(function() {
        // "obj" refers to the original class instance            
        with (this){
            // "this" now also refers to the original class instance
        }
    });

}
Share:
68,869
Jimmy
Author by

Jimmy

Updated on February 11, 2020

Comments

  • Jimmy
    Jimmy over 4 years

    I'm just getting into using prototypal JavaScript and I'm having trouble figuring out how to preserve a this reference to the main object from inside a prototype function when the scope changes. Let me illustrate what I mean (I'm using jQuery here):

    MyClass = function() {
      this.element = $('#element');
      this.myValue = 'something';
    
      // some more code
    }
    
    MyClass.prototype.myfunc = function() {
      // at this point, "this" refers to the instance of MyClass
    
      this.element.click(function() {
        // at this point, "this" refers to the DOM element
        // but what if I want to access the original "this.myValue"?
      });
    }
    
    new MyClass();
    

    I know that I can preserve a reference to the main object by doing this at the beginning of myfunc:

    var myThis = this;
    

    and then using myThis.myValue to access the main object's property. But what happens when I have a whole bunch of prototype functions on MyClass? Do I have to save the reference to this at the beginning of each one? Seems like there should be a cleaner way. And what about a situation like this:

    MyClass = function() {
      this.elements $('.elements');
      this.myValue = 'something';
    
      this.elements.each(this.doSomething);
    }
    
    MyClass.prototype.doSomething = function() {
      // operate on the element
    }
    
    new MyClass();
    

    In that case, I can't create a reference to the main object with var myThis = this; because even the original value of this within the context of doSomething is a jQuery object and not a MyClass object.

    It's been suggested to me to use a global variable to hold the reference to the original this, but that seems like a really bad idea to me. I don't want to pollute the global namespace and that seems like it would prevent me from instantiating two different MyClass objects without them interfering with each other.

    Any suggestions? Is there a clean way to do what I'm after? Or is my entire design pattern flawed?

  • Rob Van Dam
    Rob Van Dam over 14 years
    I really like this functionality but in a jQuery environment I'd be inclined to name it something else given the existing jQuery.bind method (even though there's no actual naming conflict).
  • Rob Van Dam
    Rob Van Dam over 14 years
    That won't work, by the time you get to this.doSomething, this has already been replaced by jQuery with one of the elements.
  • icktoofay
    icktoofay over 14 years
    Yeah, it had two problems when I originally posted it. I edited it, and now it should work. (sorry about that...)
  • Christian C. Salvadó
    Christian C. Salvadó over 14 years
    @Rob, it's trivial to make a jQuery.bind method that behaves in the same way as Function.prototype.bind, check this simple implementation: jsbin.com/aqavo/edit although I would consider changing its name, it could cause confusion with the Events/bind method...
  • Amit Patil
    Amit Patil over 14 years
    I would strongly recommend sticking with the name Function.prototype.bind. It's now a standardised part of the language; it's not going away.
  • Christian C. Salvadó
    Christian C. Salvadó over 14 years
    @bobnice: Totally agree, the native implementation will be available soon in major JavaScript engines... bugs.webkit.org/show_bug.cgi?id=26382 bugzilla.mozilla.org/show_bug.cgi?id=429507
  • Rob Van Dam
    Rob Van Dam over 14 years
    Good to know about the browser bugs. FYI, jQuery 1.4 now includes jQuery.proxy with similar (although not identical) functionally. Use like this $.proxy(obj.fx, obj) or $.proxy(obj, "fx")
  • Jimmy
    Jimmy almost 13 years
    The with statement is to be avoided due to ambiguity and other problems.
  • Ali C
    Ali C over 12 years
    Sure, if you can avoid using it then by all means use a simpler method, but it's still valid and useful when all else fails.
  • luke1985
    luke1985 over 10 years
    This is a big downside of JS. In big projects it makes the code look like mess. The closures idea is one of the worst ideas brought to JS. It seems that one simply can't bind the context of an object prototype to the actuall object.
  • Lazerblade
    Lazerblade over 10 years
    This is more complex than it needs to be really. +1 anyway, but I found a simpler explanation. My issue was having a callback passed as a string into a prototype function. In order for that callback, which was also part of the prototype, to properly use "this", I had to bind "this" to the prototype function when using it as a callback. Example: if (callback) { var func = this[callback]; if (typeof func === 'function') { var thefunc = func.bind(this); thefunc(); } }
  • Bergi
    Bergi about 10 years
    -1: with does not change the value of this
  • dhimes
    dhimes over 9 years
    This is a good method, but there's a further, more subtle issue with it that may present in some cases. When you return a method with your constructor, the new operator no longer even returns the prototype chain. That is, it's not a matter of being hidden or overwritten- it's not there. Any members you did have on the prototype chain- say, from super-classes, are gone.
  • Nemesarial
    Nemesarial over 9 years
    @dhimes - actually the only reason you cannot access the prototype chain is because you no longer have access to the constructor function. Except we do have access to it via <function>.constructor property. Test this out for proof: a=new function(){}; a.constructor.prototype.b=function(){console.log('in .b');}; a.b();