0

I'm trying to use call to pass the context of the Utilities object so I can access its members (allData array, etc) within the myTest function.

I'm getting error:

ReferenceError: allData is not defined

This tells me the context is lost and I guess I'm not binding the context correctly. How do I do this?

var Utilities = {
    allData : [],
    storage : [],

    apiRequest : function () {
        this.allData = ["a","b","c"];
        var myTest = this.allData.map.call(this, function (i, el) {
            var url = 'testPHP.php/translate_tts?ie=utf-8&tl=zh-CN&q=' + i;
            return $.get(url, function (returned_data) {
                this.storage.push(returned_data);
            });
        });

        $.when.apply($, myTest).done(function () {
            log('done!');
            log(this.storage[i]);
        });
user3871
  • 12,432
  • 33
  • 128
  • 268
  • 3
    The error message you see has nothing to do with context. You're simply referencing a variable that doesn't exist. `allData` isn't a variable - it's a property of `Utilities`. So use `Utilities.allData` or possibly `this.allData`. Depending on how you call the `apiRequest` method, `this` may not be correct to use. – Ian Jun 20 '15 at 05:12
  • @Ian okay. If I wanted to reference the properties of Utilities within another function where the context is lost, how would I do that? I thought `call` binds the context – user3871 Jun 20 '15 at 05:23
  • Let me ask you this - is this your actual code, or an example? First thing is that a lot of this isn't necessary (can be done in a different way) to overcome problems with `this`. Second thing - your most recent edit - `this.storage` is in a nested function, where the value of `this` is now not the same as the `.map.call()`'s – Ian Jun 20 '15 at 05:29
  • 1
    You're right that `call` binds the context, but you're confusing what's being bound. In your code, for example, your code `this.allData.map.call(this ...` is saying "take `this` (`Utilities`) and call `.map()` on it". That doesn't make sense. `this.allData.map` is a generic reference to the `Array.prototype.map` function, and by using `.call()`, you're telling it what to loop over - `this`. `call()` isn't as useful for built-in functions, but more helpful for calling created functions with different contexts...and often can be replaced with by using parameters – Ian Jun 20 '15 at 05:33
  • 1
    Hopefully this helps: http://jsfiddle.net/621me9uu/ – Ian Jun 20 '15 at 05:38
  • 1
    @Ian very helpful, thank you. I get confused with context in js – user3871 Jun 20 '15 at 05:41
  • @Growler— *this* isn't "context". It's one property of an execution context that is set by how a function is called or through the use of *bind*. You can't pass an execution context, or even reference it. You can assign and bind to the object assigned to *this* though. – RobG Jun 20 '15 at 06:04
  • 1
    @RobG: Uh oh, I always deliberately name it "*`this` context*"? Most people confuse `this` with the scope, and I think "context object [for the call]" is an appropriate term to clear up the concept. I don't think anyone who knows about the language spec term "execution context" has problems with the `this` keyword :-) – Bergi Jun 20 '15 at 14:31
  • @Bergi—I feel that calling *this* "context" infers that its value is related to where a function is called from (i.e. the context of the call), when it is completely independent of that. It's a parameter, so why not just call it that? – RobG Jun 20 '15 at 23:39
  • @RobG: Yeah, I mean a context *for* the call not a context of the call - the object *on* which the function is called. Of course, we can technically call it the *thisArg* parameter, but I don't think anyone who hasn't read the spec understands that, or can infer a purpose from that term. – Bergi Jun 21 '15 at 12:14

2 Answers2

2

Reminder

There is only function level context in Javascript, and this is nothing more than a local variable. By default, this is set to the global window object:

function me () { return this; };
me() === window; // true

Or to the object from which the function was invoked:

var o = {};
o.me = me;
o.me() === o; // true

Knowing this, read the following carefully:

var me = o.me;
me === o.me; // true, not a copy
me() === o; // false
me() === window; // true

var p = {};
p.me = o.me;
p.me() === p; // true
o.me() === o; // true

As you can see, this is automatically set at function invocation. This is the default behaviour, but you can also do it yourself using either .call() or .apply() (one shot):

me.call(o) === o; // true
me.apply(o) === o; // true
p.me.call(o) === o; // true
me() === window; // true

And more recently, .bind() (permanent):

me = me.bind(o);
me() === o; // true
me() === window; // false

Your question

I would use .bind():

var Utilities = {
    allData : [],
    storage : [],

    apiRequest : function () {
        this.allData = ["a","b","c"];
        var myTest = this.allData.map(function (i, el) {
            var url = 'testPHP.php/translate_tts?ie=utf-8&tl=zh-CN&q=' + i;
            return $.get(url, function (returned_data) {
                this.storage.push(returned_data);
            }.bind(this));
        }.bind(this));

        $.when.apply($, myTest).done(function () {
            log('done!');
            // I've added a loop here
            var i, l = arguments.length;
            for (i = 0; i < l; i++) {
                log(this.storage[i]);
            }
        }.bind(this));
Community
  • 1
  • 1
1

Great answer above. One thing to emphasize at this point: whenever you bind the object at initialization it will be bound and cannot be call/apply'd using another context anymore. IMO it's up to you whether to use call/apply (at runtime) OR .bind (permanently).

Going from here:

I'm trying to use call to pass the context of the Utilities object so I can access its members (allData array, etc) within the myTest function.

var OtherTest = function() {
    this.dataStorage.push(["ok"]);
    console.log(arguments);
}

var Utilities = {
    dataStorage: [],
    allData: [],
    apiRequest: function() {
        this.allData = ["a","b","c"];

        OtherTest.apply(this,arguments);

    }
}


Utilities.apiRequest('hu','hu');
console.log(Utilities.dataStorage[0]);

Since this is a reference to the object, it can be mutated at any time after initialization making it easy to use call/apply to pass the context which is the Utilities Object in this case.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
F4b
  • 95
  • 7