Chi Chan
Chi Chan

Reputation: 12410

How to keep 'this' in a class from within a javascript closure

I have read this answer and IIFE but I can't seem to find the correct solution to my problem.

I have a simple class here:

define(['jquery'], function($) {
    // Need 'self' because someCallback() is being called with .call() and 'this' changes
    var self;

    function Foo(number) {
        self = this;
        this.someNumber = number;
    }

    Foo.prototype = {
        someCallback: function () {
            //Use self because 'this' changes to a DOM element
            var num = self.someNumber;

            //Do something with the num
            return num * 2;
        }
    };

    return Foo;
});

and someCallBack() is being called by a jQuery plugin using .call(). Because of this, the context changed, hence the use of the self variable.

However, this is wrong because:

define(['foo'], function(Foo) {
    describe('context question', function () {
        var foo1 = new Foo(1);
        var foo2 = new Foo(2);

        it('"this" should work', function () {
            var call1 = foo1.someCallback.call(this); // 4
            var call2 = foo2.someCallback.call(this); // 4

            expect(call2).toBe(4); // Only works because it is 'new' last
            expect(call1).toBe(2); // Fails because 'self' is taken from foo2
        });
    });
});

How exactly should I wrap the self variable to make this code work?

Upvotes: 1

Views: 59

Answers (2)

traktor
traktor

Reputation: 19356

Two ways of calling an object method which stores its own this value include

  1. Define the method as a nested function which references its this value in a closure which stores this value in a variable. The function defined could be anonymous or declared with a name but must be evaluated each time a class instance is created, so as to create a new Function object capturing different values of self in function scope.

  2. Take a statically defined function object and bind its this value using bind. Bind creates a new wrapper function object each time it is called.

The first method looks like (without Jquery or Jasmine):

function Foo(number)
{   var self = this;
    this.num = number;
    this.someCallback = function()  // method with new Foo object stored as self in function scope 
    {   // something with num:
        return self.num * 2;
    }
}

and the second method could look like

function Foo(number)
{   this.num = number
    this.someCallback = this.someCallback.bind(this); // bind prototypical method as local method.
}

Foo.prototype = {
    someCallback: function () {
        // this value is bound by constructor;
        //Do something with the num
        return this.num * 2;
    }
};

Upvotes: 1

Josh Beam
Josh Beam

Reputation: 19792

You could probably just use the revealing module pattern and declare it as a "global" variable (local to the module):

define(['jquery'], function($) {
    var someNumber;

    function Foo(number) {
        someNumber = number;
    }

    Foo.prototype = {
        someCallback: function () {
            return someNumber * 2;
        }
    };

    return Foo;
});

Upvotes: 1

Related Questions