1

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?

Community
  • 1
  • 1
Chi Chan
  • 11,890
  • 9
  • 34
  • 52

2 Answers2

1

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;
});
Josh Beam
  • 19,292
  • 3
  • 45
  • 68
  • I tried the code but it doesn't work. The problem is that `someNumber` will change after I call `new Foo(2)`. – Chi Chan Feb 22 '16 at 19:30
1

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;
    }
};
traktor
  • 17,588
  • 4
  • 32
  • 53
  • Thanks. I tried the 2nd method, but it doesn't work. I think the `.call()` is changing `this`. The 1st method works great. However, is that a "proper" thing to do? From what I know, this will recreate `someCallback()` over and over again for each `Foo`. Is this common? (sorry, still kind of new with creating objects in JS) – Chi Chan Feb 22 '16 at 19:58
  • If copied and pasted the second method would have failed due to upper casing of "Back" in "someCallback" whereas it should have been lower case. I've updated the second code example - can you confirm if it still fails please? Even so it is quite common to create new function objects in an object constructor, although possibly sometimes due to programming style rather than necessity ;-) – traktor Feb 22 '16 at 21:53