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

8
  • @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"). Commented Jan 29, 2014 at 6:26
  • 1
    Hmm, I'm gonna make a quick fiddle. I think I get what you're asking Commented Jan 29, 2014 at 6:28
  • You misunderstood how this works in JavaScript. MDN this - JavaScript Commented Jan 29, 2014 at 6:41
  • Also look at JavaScript “this” keyword Commented Jan 29, 2014 at 6: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. Commented Jan 29, 2014 at 6:49

3 Answers 3

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});
Sign up to request clarification or add additional context in comments.

2 Comments

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 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
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

1 Comment

My subjective opinion: Only use this keyword in function context and only when it's object method or in constructor.
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"

2 Comments

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?
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!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.