2

I'm trying to refactor a chunk of code that acts on each variable found in the variables array through by means of putting the variable name and its corresponding callback into an object that is easily iterated through. The problem is that variables[0].callback ends up undefined when calling Foo.bar(data). The functionality of the entire object needs to remain in the object itself.

So far my only solution has been to define the functions when they are assigned to the callback variables but this is less than ideal because the callbacks themselves cannot access the required variables.

...
{name: "x", callback: function() {...}},
...

Desired Functionality:

console.log(Foo.variables[0].callback != undefined) //true
Foo.bar(); //Will print "y"

Am I missing something silly? Or should I be using another pattern to tackle this particular problem?

Thanks for the help!

var Foo = {

    variables: [
        {name: "x", callback: this.funX},
        {name: "y", callback: this.funY},
        {name: "z", callback: this.funZ}
    ],


    //For this example, data could be the following object
    // data = {x: 0, y: 1, z: 0};
    bar: function(data) {

        this.variables.forEach(function(elem) {
            if(data[elem.name] == 1)
                elem.callback();
        });

    },

    funX: function() {
        console.log(this.variables[0].name);
    },

    funY: function() {
        console.log(this.variables[1].name);
    },

    funZ: function() {
        console.log(this.variables[2].name);
    }
}

Somewhat relevant:

javascript: call function inside object, from a callback function

Community
  • 1
  • 1
TMan
  • 1,775
  • 1
  • 14
  • 26
  • @CoryDanielson - My apologies - I'll edit the post and make it clearer. In the mean time, I want to call Foo.bar(data) and have it console.log("y"). – TMan Jan 29 '14 at 06:26
  • 1
    Hmm, I'm gonna make a quick fiddle. I think I get what you're asking – Cory Danielson Jan 29 '14 at 06:28
  • You misunderstood how `this` works in JavaScript. [MDN this - JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) – Givi Jan 29 '14 at 06:41
  • Also look at [JavaScript “this” keyword](http://stackoverflow.com/questions/3127429/javascript-this-keyword) – Givi Jan 29 '14 at 06:46
  • @Givi - by all means correct me. `this` clearly does not work in the `variables` array which is what I'm trying to fix but otherwise, I'm not sure you are correct. – TMan Jan 29 '14 at 06:49
  • Yes, it's because `this` refer to a window object (in your case). To better understanding how `this` works in JavaScript read great answer of [Alan Storm](http://stackoverflow.com/users/4668/alan-storm) - [How does “this” keyword work within a JavaScript object literal?](http://stackoverflow.com/a/134149/2029693) – Givi Jan 29 '14 at 07:19
  • @Givi - right... In the original array that was the problem but elsewhere, everything functions as desired. I wasn't clear on why `this` seemed to work everywhere else but there but I guess you're saying it's because it is in reference to a new object within the object? – TMan Jan 29 '14 at 07:36
  • I tried to answer your question. [See below](http://stackoverflow.com/a/21426337/2029693) – Givi Jan 29 '14 at 09:19

3 Answers3

3

LIVE DEMO

Citing:

//For this example, data could be the following object
// data = {x: 0, y: 1, z: 0};

so let's go with Foo.bar({x: 0, y: 1, z: 0});

example:

var Foo = {

    variables: [
        {name: "x", callback: "funX"},
        {name: "y", callback: "funY"},
        {name: "z", callback: "funZ"}
    ], 
    bar: function(data) {
        var that = this;              // this (Foo) reference!
        that.variables.forEach(function(el, i, arr) {
          if(data[el.name] == 1){
             var fn = arr[i].callback; // funY
             that[fn]();     // "y" (cause that's exactly what funY does!!)
          }
        });
    },
    funX: function() {
        console.log(this.variables[0].name);
    },
    funY: function() {
        console.log(this.variables[1].name);
    },
    funZ: function() {
        console.log(this.variables[2].name);
    }
}; 


Foo.bar({x: 0, y: 1, z: 0});
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • Awesome, thanks! Is it considered bad practice to call things from `window` directly? If so, is there any way to make this work so if `Foo` was to be named something else, everything would still work without a hitch? – TMan Jan 29 '14 at 07:08
  • 1
    @TMan if you don't like the call on the `window` object () than don't use it, create before your forEach a reference to the `this` (`Foo`) object and use it instead of `window` :) -- edited my answer accordingly – Roko C. Buljan Jan 29 '14 at 07:16
2

Answer to comment
No. I'm not sure that I could explain well, but still try. So, this is function's keyword, its value depends in which context it's used and it also has some differences between strict mode and non-strict mode.

If it's used in Global context this will refer to window object in strict and non-strict mode.
Strict mode:

"use strict";
console.log(this); // window

Non-Strict mode:

console.log(this); // window

If it's used in Function context the value maybe differ from where this function is called, if it's called from Global context its will refer to window object in non-strict mode, and undefined in strict mode.

Strict mode:

(function() {
    "use strict";
    console.log(this); // undefined
}());

Non-Strict mode:

(function() {
    console.log(this); // window
}());

When a function is called as a method of an object, its this is set to the object the method is called on.
So, in your case:

var Foo = {
    /* ... */
    bar: function(data) {
        // this refer to `Foo`
        this.variables.forEach(function(elem) {
            // but, here `this` refer to window in non-strict mode and undefined in strict mode
            if(data[elem.name] == 1)
                elem.callback();
        });

    }/*, ... */
};

and, if your object Foo is declared in global context its will refer to window object in strict mode and in non-strict mode too:

var Foo = {
    variables: [
        {name: "x", callback: this.funX}, // this === window 
        {name: "y", callback: this.funY}, // same
        {name: "z", callback: this.funZ}  // ...
    ]/*, ... */
};

but, if it's declared in function scope and function called from global context:

function myFunction() {
    var Foo = { 
        variables: [
            // this will refer to window in non-strict mode and undefined in strict mode
            {name: "x", callback: this.funX}/*, ... */            
        ]/*, ... */
    }; 
}
myFunction();  

but, if it's a object method and there's no difference in which context is object declared:

var myObj = { 
    myMethod : myFunction  // now this will refer to myObj
};

Since, you can invoke function as a constructor with new operator, in such case this will refer to an instance of that constructor (class) and there is no difference between strict or non-strict mode.

function MyClass(v) {
    this.myProp = v; // this will refer to instances of MyClass
}
var myClass1 = new MyClass("some value");
var myClass2 = new MyClass("other value");

console.log(myClass1.myProp); // "some value";
console.log(myClass2.myProp); // "other value";

Also, you can bind explicity this with Function.prototype.call, Function.prototype.apply and with Funciton.prototype.bind

var myObj = {
    x: 5,
    y: 6
};

function sum() {
    return this.x + this.y;
}

console.log(sum.call(myObj)); // 11
console.log(sum.apply(myObj)); // 11
console.log(sum.bind(myObj)()); // 11
Community
  • 1
  • 1
Givi
  • 1,674
  • 2
  • 20
  • 35
  • *My subjective opinion: Only use `this` keyword in function context and only when it's object method or in constructor.* – Givi Jan 29 '14 at 09:05
1

You want to be using a different pattern. JS these days has become pretty complicated, and there are many benefits to being object oriented. It makes the scope unambiguous, and the order of execution less finicky.

While you could get the above to work, a better implementation would look something like this:

var Foo = function() {
  // you can replace the anonymous functions with references if you like
  this.variables = [
    'x': new Variable('x', function() { console.log('x something'); }),
    'y': new Variable('y', function() { console.log('y something'); }),
    'z': new Variable('z', function() { console.log('z something'); })
  ]
};

// Now data can be like this: ['x', 'y'] // much clearer!
Foo.prototype.bar = function(data) {
  data.forEach(function(symbol) {
    var variable = this.variables[symbol];
    if (variable)
      variable.callback.apply(this); // much safer!
  }, this);
};

// A better labeled pair.
var Variable = function(name, callback) {
  this.name = name;
  this.callback = callback;
};

An example usage might look something like this:

var foo = new Foo();
foo.bar(['x']);     // "x something"
foo.bar(['y', 'z']) // "y something" \ "z something"
adu
  • 947
  • 1
  • 8
  • 15
  • Thanks for the response @adu - will this still work if I want to define the callbacks outside of the assignment? For example, I can get the original code above working by defining the functions inline just as you have in the example you provided. Slightly different note but I was told a few months ago that prototypical patterns were becoming a bit oldschool, for lack of a better term, and that other patterns were preferred. Is this not the case? – TMan Jan 29 '14 at 06:44
  • 1
    Yes, they will still work. Let me know if you want an example. The actual `prototype` keyword is going out of style. But this is because frameworks like BackboneJS, Ember, and the CoffeeScript language are becoming more prevalent. They are a little heavy for your question, but are all object oriented! – adu Jan 29 '14 at 06:48