4

Sorry if this has been answered already, but I could not find an appropriate answer on here.

I've started writing my javascript code in a modular style lately and I have a question regarding how module variable scope works.

The following code gives me a conflicting answer.

I have a module named Base that declares two strings and an array. It also has a function called fetchData that uses the jQuery getJSON shortcut to set these variables with server data. Unfortunately when I ask for Base's string1 or string2, I get undefined. I understand that this is probably due to the fact that I have it set their values two functions deep (inside the AJAX callback and inside fetchData) and the scope limits it from seeing Base.string1 and Base.string2.

However, when I look at Base.array1 from outside the module, it's set to the appropriate data I pulled from the server, even though it's set from the same scope as the strings.

Here's the code:

namespace.Base = (function(){
    var string1, string2, array1 = [];
    function fetchData(){
        $.getJSON('backendScript.php', function(data){
            string1 = data.string1;
            string2 = data.string2;
                arrayCount = data.arr.length;
                for(var i = 0; i<arrayCount; i++){
                    array1[i] = data.arr[i];
                }
         })
     }
     return{
         fetchData: fetchData,
         string1: string1,
         string2: string2,
         array1: array1
     }
})();

If I change

string1 = data.string1;

to

namespace.Base.string1 = data.string1;

it works like I want.

So my question is, why is array1 set correctly when it's set from the same scope as the strings?

Also, what is the remedy for setting module-level variables from within the module's functions without having to give a global path (e.g. namespace.Base.string1)?

Dan
  • 195
  • 1
  • 1
  • 7
  • Remember to upvote *all* answers which are of help to you. Check the one which best answers your question. If none are "check-worthy" then just do the upvotes for all of the helpful answers. – Larry K Nov 04 '10 at 15:55
  • I'm new here, so I'll upvote as soon as I have 15 reputation. – Dan Nov 05 '10 at 00:46
  • Hmmm, good point. Welcome to Stack Overflow. – Larry K Nov 06 '10 at 23:53

3 Answers3

3

The problem is that you actually have two different references, the variable string1 within the closure of the anonymous function that you invoke to create namespace.Base, and namespace.Base.string1, which is on the object returned from that anonymous function. Your assignment of the variable string1 to the object property string1 is a one-time set, not a live reference. Further modification of the variable string1 will not affect the object property. Here's what you want:

namespace.Base = (function() {
  var my = {
    string1: null,
    string2: null,
    array1: [],
    fetchData: function () {
      $.getJSON('backendScript.php', function(data){
        my.string1 = data.string1;
        my.string2 = data.string2;
        var arrayCount = data.arr.length;
        for (var i = 0; i < arrayCount; i++){
          my.array1[i] = data.arr[i];
        }
      });
    }
  };
  return my;
})();

Now the local, but public, members of namespace.Base are in the object my. You can create private variables using var within the anonymous function, or create more public properties by adding them to my.

bcherry
  • 7,150
  • 2
  • 28
  • 37
  • Thanks! I can understand how my variables were being returned before being set by fetchData. I was incorrectly thinking of them as live references or like properties of an object. I'm beginning to think that the module pattern isn't worth using versus creating things as objects. I understand the concept of having private variables and choosing which variables to allow access through an API (the return), but it seems that to create dynamic variables I have to use the object pattern anyway. – Dan Nov 04 '10 at 08:41
  • Also, is creating an object named "my" a standard practice when creating modules? I seem to remember seeing that before and not completely understanding it. – Dan Nov 04 '10 at 08:42
  • I often use `my` for this purpose. Sometimes `that` or other terms are used. I think `my` is a bit clearer. It's true that the module pattern is imperfect, since you still have to use objects to keep local references to public properties. It's up to you whether you use it or not, but the private properties are sometimes handy to have. – bcherry Nov 04 '10 at 18:29
1

I would be a good idea to get familiar with closures and how they work:

How do JavaScript closures work?

Community
  • 1
  • 1
Slappy
  • 4,042
  • 2
  • 29
  • 41
0

Your "scope" issue is not actually a scope issue. The issue is that arrays are pointers to their data, strings are not.

namespace.Base is set to the results (returned value) of the anonymous function. -- It is set to be an object containing a function ref (fetchData), two empty strings and an array.

If you later call the fetchData function, then it will change the contents of array1. But it will also create two new strings (from data.string1 and data.string2). The old values of string1 and string2 (which are namespace.Base.string1 and namespace.Base.string2) are not changed. So they are left as empty strings (not what you want).

Example of this. Try it in Firebug--

s1 = "Hi";
s2 = s1;  // s2 => "Hi"
s1 = "Bye" 
alert(s2); // *** s2 is still "Hi", it was not changed!

// But arrays are different:
a1 = ["Hi"];
a2 = a1;
a1[0] = "Bye";
alert(a2[0]); // a2[0] is now "Bye"  

Added: Asynch Timing error

Also, note that your code is wrong as written since you're not giving the caller any way to know when the Ajax call has completed:

namespace.Base.fetchData(); // starts the Ajax call via getJSON method
var a = namespace.Base.array1; // ERROR!! The value of namespace.Base.array1 is
                               // indeterminate since you don't know if the
                               // the Ajax request has completed yet or not!

You appear to be trying to convert the asynchronous Ajax call (which invokes a callback function once the answer has been received from the remote server) into a synchronous call which will not return until the results have been received.

This is a really bad idea. (If you want to know more, ask another question in SO.)

Larry K
  • 47,808
  • 15
  • 87
  • 140
  • Thanks! Beginning to understand closures better, but I was also unaware of the fact that arrays work that way... very insightful example. – Dan Nov 04 '10 at 07:35