8

I need some help understanding the NodeJs. I'm obviously missing something fundamental. I've got a module similar to the following, using a basic revealing module pattern...

var someArray = [];

var publicApi = {
    someArray: someArray,
    someFunction: someFunction
};

function someFunction() {
    someArray = ['blue', 'green'];
}

module.exports = publicApi;

When I consume this module, someArray is not changed when I call someFunction...

var myModule = require('myModule');

myModule.someFunction();
var test = myModule.someArray;
// test is always an empty array

Please help me understand why. I realize I can probably get this to work using a constructor, but I want to fill the gap in my knowledge of why the above does not work.

Update:

I can get this to work with the following modifications to the module...

var theModule = {
    someArray: [],
    someFunction: someFunction
};

function someFunction() {
    theModule.someArray = ['blue', 'green'];
}

module.exports = theModule;

but I'd still like to understand exactly why the code in the first module did not work. The module I'm working on is fine as a singleton, so I'd love to see what is considered best practice for having variables in a module that can be altered by the functions in that module, and be publicly accessible outside that module.

Tim Hardy
  • 1,654
  • 1
  • 17
  • 36

4 Answers4

4

The reason the first way you did it didn't work is the same reason it wouldn't work doing the same in JavaScript without Node:

var someArray = [];

var object = {
    someArray: someArray,
}
someArray = [1, 2, 3];
console.log(object.someArray);

This prints [] because object.someArray is a reference to the first array you created. This is the process:

var someArray = [];

Create an empty array and then save a reference to that array as the name someArray. Let's call this array1.

var object = {
    someArray: someArray
}

Create an object with a property someArray, make that property reference the array that someArray references. It's important to know that this means this reference is now a reference to array1, not to someArray. This leads us to:

someArray = [1, 2, 3];

Which creates a new array (let's call it array2), which it then stores as someArray. This array is completely independent of array1 and all future references of someArray will get array2, but it has no effect on previous access.

This works exactly the same as your Node example - when you overwrite someArray rather than publicApi.someArray, you make no changes to publicApi.someArray, so you can't expect it to be different.

To hopefully make this clear, you go from the following:

someArray -> array1[]
object.someArray -> array1[]

To this:

someArray -> array2[1, 2, 3]
object.someArray -> array1[]

Note that object.someArray is unchanged.

Vala
  • 5,628
  • 1
  • 29
  • 55
  • 1
    Both this and csum's answer are accurate, but this one is a little clearer. My paraphrase is that my publicApi.someArray remains pointing to the original, empty array, which is completely unaltered by the statement someArray = ['blue', 'green']. That statement changes what someArray is pointing to, but it does not change what publicApi.someArray is pointing to. – Tim Hardy Nov 25 '15 at 17:24
0

You are thinking about this as if it were an object rather than a closure. A closure results when a function has access to privileged data. That is, the data binding "disappears" from the rest of the program. Here's an example:

function SomeFunction(x) {
   var closureVar = x;

   toReturn = function(y) {
     var answer = closureVar;
     closureVar = y;
     return answer;
   }

   return toReturn;
}

var runIt = SomeFunction(15);

What happens is that when SomeFunction is called, it creates a local binding for "closureVar". When a function exits, all of its local bindings (usually) disappear. However, in this case the returned function contains a reference to 'closureVar', so it can't be completely deleted. Thus, the function defined as 'toReturn' can still use it.

However, nothing else in the program can get to it. If you call SomeFunction() again, it will create a new (local) binding for closureVar, which will be used by the toReturn function given by that call to SomeFunction(). Each call to SomeFunction() results in a new closure.

  • That makes sense for closures, but maybe my problem has nothing to do with closures. Perhaps I wrote my question title improperly. My question is more directed at Node modules and maybe what I created isn't a closure at all. Hence my confusion. I need to understand what Node is doing with the code I wrote above. – Tim Hardy Nov 19 '15 at 15:59
  • Tim, it's still a variable scope issue. The array you're trying to change has a full name of 'MyModule.someArray'. Your function is creating a new, local variable called 'someArray'. If you want it to reference the someArray that you defined in the module, you need to use `MyModule.someArray = ['blue', 'green'];` – A.Primus Nov 19 '15 at 16:10
  • That's not correct. If I change the declaration of someArray to var someArray = ['yellow']; then when I debug into someFunction, someArray will begin with the value of ['yellow']. In addition, in WebStorm, someArray is listed under the "Closure" variables, not the "Local", so it's definitely tied to that initial variable. You're welcome to try it. – Tim Hardy Nov 19 '15 at 16:36
  • @TimHardy That's true, `someArray` is fully accessible from within `publicApi`, but it's only used to *initialise* `publicApi.someArray`, it's not inexorably linked to `someArray`. Any changes to the array that `someArray` references will be reflected in `publicApi.someArray` because they're *at the moment* referencing the same array. However, when you do `someArray = [...]`, you make `someArray` be a *different* array and you can no longer change the value of `publicApi.someArray` through `someArray`. I've tried to clarify in my answer. – Vala Nov 21 '15 at 21:43
0

FWIW, it does actually work if you do it like this:

function someFunction() {
    //someArray = ['blue', 'green'];
    someArray[0] = 'blue'
    someArray[1] = 'green'
}

i think this means that when you create a new array with [] you broke the reference chain somehow. my understanding is not good enough to say yet.

0

Probably the best practice for accessing variables within a module would be to define get/set methods:

var someArray = [];

var publicApi = {
    getSomeArray: function ()  { return someArray; },
    setSomeArray: function (s) { someArray = s; },
    /*
     * Or if you know you can support get/set syntax:
     *
     * get someArray ()  { return someArray; }
     * set someArray (s) { someArray = s; }
     */
    someFunction: someFunction
};

function someFunction() {
    someArray = ['blue', 'green'];
}

module.exports = publicApi;

Get and set syntax definition on MDN.

But if you do want direct access to the object itself, use the module namespace as you mentioned:

theModule.someArray = ['blue', 'green'];

But the issue you are seeing occurs because you are replacing with a new array, rather than modifying the array, e.g.

function someFunction() {
    someArray.splice(0);
    someArray.push('blue', 'green');
}

I believe assignment causes the creation of a new object reference, while modifying the existing object maintains the existing reference.

This is a result of Call-by-sharing:
Is JavaScript a pass-by-reference or pass-by-value language?
https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing

Community
  • 1
  • 1
csum
  • 1,782
  • 13
  • 15