2

I'm trying to create a function which returns another function. I want separate information when each of the inner function is run, but this isn't happening. I know that explanation is not great, so I've put together a small example.

            var testFn = function(testVal) {
                return (function(testVal) {
                    var test = testVal;

                    this.getVal = function() {
                        return test;
                    }

                    return that;

                })(testVal);
            }
            var a = testFn(4);
            var b = testFn(2);
            console.log(b.getVal(), a.getVal());

This outputs 2, 2. What I would like is 2, 4 to be output. I know this isn't explained perfectly, so if it's not clear what I'm trying to achieve, can someone explain why the variable seems to be shared across the two functions?

Thanks

Adam
  • 5,091
  • 5
  • 32
  • 49

3 Answers3

3

Like this ?

var testFn = function(testVal) {
  var test = testVal
  return {
    getVal: function() {
      return  test
    }
  }   
};

var ab = testFn (4)
var ac = testFn (2)

console.log(ab.getVal(),ac.getVal()) //4 //2

The problem in your code is this.getVal() / returning this

because 'this' refers to the global scope / Window

You are glubbering with the global namespace and overwriting Window.getVal() , the moment you are setting b = testFn (2) This results in overwriting as method getVal too because they both refer to the global Object and always share the same method getVal

Therefore they share the same closure and are outputing 2

console.log("The same: " + (Window.a === Window.b)) // true
console.log("The same: " + (a === b)) // true

you can see that if you change it a little:

        var testFn = function(testVal) {
          var x = {}
            return (function(testVal) {
                var test = testVal;
                x.getVal = function () {
                  return test;
                }
                 return x

            })(testVal);
        }
        var a = testFn(4);
        var b = testFn(2);
        console.log(b.getVal(), a.getVal());//4 2

it suddenly works because it results in 2 different Objects returned (btw you don't even need the outer closure) console.log("The same: " + (a === b)) // false

Here are the JSbins First / Second

I hope you understand this, I'm not good in explaining things If theres anything left unclear, post a comment and I'll try to update the answer

Moritz Roessler
  • 8,542
  • 26
  • 51
2

This question comes down to the context in which functions are invoked in JavaScript.

A function that is invoked within another function is executed in the context of the global scope.

In your example, where you have this code:

var testFn = function(testVal) {
  return (function(testVal) {
    var test = testVal;
    this.getVal = function() {
      return test;
    }
    return this;
  })(testVal);
}

The inner function is being called on the global scope, so this refers to the global object. In JavaScript a function executed within another function is done so with its scope set to the global scope, not the scope of the function it exists within. This tends to trip developers up a fair bit (or at least, it does me!).

For argument's sake, lets presume this is in a browser, so hence this refers to the window object. This is why you get 2 logged twice, because the second time this runs, this.getVal overwrites the getVal method that was defined when you ran var a = testFn(4);.

JavaScript scopes at function level, so every function has its own scope:

var x = 3;
function foo() {
  var x = 2;
  console.log(x);
};
console.log(x); //gives us 3
foo(); // logs 2

So what you want to do is run that inner function in the context of the testFn function, not in the global scope. You can run a function with a specific context using the call method. I also recorded a screencast on call and apply which discusses this in greater detail. The basic usage of call is:

function foo() {...}.call(this);

That executes foo in the context of this. So, the first step is to make sure your inner function is called in the right context, the context of the testFn method.

var testFn = function(testVal) {
  return (function(testVal) {
    var test = testVal;
    this.getVal = function() {
      return test;
    }
    return this;
  }.call(this, testVal);
}

The first parameter to call is the context, and any arguments following that are passed to the function as parameters. So now the inner function is being called in the right scope, it wont add getVal to the global scope, which is a step in the right direction :)

Next though you also need to make sure that every time you call testFn, you do so in a new scope, so you're not overwriting this.getVal when you call testFn for the second time. You can do this using the new keyword. This SO post on the new keyword is well worth reading. When you do var foo = new testFn() you create and execute a new instance of testFN, hereby creating a new scope. This SO question is also relevant.

All you now need to do is change your declaration of a and b to:

var a = new testFn(4);
var b = new testFn(2);

And now console.log(b.getVal(), a.getVal()); will give 2, 4 as desired.

I put a working example on JSBin which should help clear things up. Note how this example defines this.x globally and within the function, and see which ones get logged. Have a play with this and hopefully it might be of use.

Community
  • 1
  • 1
Jack Franklin
  • 3,765
  • 6
  • 26
  • 34
1

The output you get is (2,2) because when you do

var that = this;

what you actually get is the global object (window),
the object that holds all the global methods and variables in your javascript code.
(Note that every variable that is not nested under an object or function is global and
every function that is not nested under an object is global, meaning that functions that are nested under a function are still global)

so, when you set:

var test = testVal;

this.getVal = function() {
    return test;
}

you actually set the function "getVal" in the global object, and in the next run you will again set the same function - overriding the first.
To achieve the affect you wanted I would suggest creating and object and returning it in the inner function (as @Glutamat suggested before me):

var testFn = function(testVal) {
        return new Object({
            getVal: function() {
                return testVal;
            }
        });
}
var a = testFn(4);
var b = testFn(2);
console.log(b.getVal(), a.getVal());

In this way, in the outer function we create an object with an inner function called "getVal" that returns the variable passed to the outer function (testVal). Here's a JSBin if you want to play around with it
(thanks to @Glutamat for introducing this site, I never heard of it and it's really cool :D)

vakninir
  • 83
  • 8
  • You're welcome =) i discovered it when jsfiddle, was down, and it turned out i like it more, mainly because the inbuilt console and the style. – Moritz Roessler Dec 10 '12 at 13:49