2

I'm trying to get a function to set a variable at top-level scope of a module, but not have it leak into global scope (because... bad). At first I thought implicit variable declarations in a module would stay in the module, but it seems like it goes to the script calling the module too.

Take for example (aModule.node.js):

var
  onlyToThisModule = true;

function createAGlobal() {
  //creates a implicit var unfortunately
  globalToEveryone = true;
}

createAGlobal(); //this will now appear in the global scope for the script that require() it. eeek.

console.log(onlyToThisModule);

exports = createAGlobal;

Then require it:

var
  aModule = require('./aModule.node.js');

console.log(globalToEveryone); //outputs "true"
console.log(typeof onlyToThisModule); //outputs "undefined"

What I would like to do is to declare a variable in createAGlobal that would not leak into the global scope. I'd like to keep it in the scope of the function that called it. I know I can put it in the var statement of the module itself, but I'm looking to declare the variables only if someone executes the function. Is there a way to achieve this type of scoping?

stockholmux
  • 1,169
  • 11
  • 24
  • 1
    You can create an empty variable first and then fill it with data once function is called. `var global; function(){ global = true}` – Eugene Kostrikov Apr 23 '14 at 19:55
  • Yes, but that only works in particular context. So, if you have a 3rd module and it requires aModule.node.js and executes createAGlobal() that wouldn't achieve what I'm trying to do. – stockholmux Apr 23 '14 at 19:59

3 Answers3

1

Why is it a requirement that the variable only gets declared if someone calls the function? If that's not a strict requirement, then you could declare it in your module scope, and initialize it in your function scope:

var onlyToThisModule; // currently `undefined`

function initVar() {
    onlyToThisModule = true;
}

console.log(onlyToThisModule); // undefined
initVar();
console.log(onlyToThisModule); // true

... if, on the other hand, you really want to declare a variable only in the function but also have it be available to the rest of your module, then you can create it as a class property:

function myClass() {
    this.onlyToThisModule = true;
}

var mc = new myClass();
console.log(mc.onlyToThisModule);

... but this would still expose the variable to the calling module, just through the intermediary of your function.

If neither of those suits your situation, then you'll have to provide more details.

Jason
  • 13,606
  • 2
  • 29
  • 40
  • Thanks but these don't meet my needs. The first example isn't modular - I need to be able to invoke `createAGlobal` in multiple different modules and declaring the variable before init would defeat the purpose. The second example is closer but the variable names are not exactly what I need. As I mentioned in another answer's comment, this is one of 50 or so variables that I need to declare with exact names. – stockholmux Apr 23 '14 at 20:23
  • I'm using the variable name from your example. I can't divine what variable names might be appropriate for your application, so you're just going to have to manage that yourself. If you're saying that you want to include your module in other modules, and each calling module has its own distinct copy of the variable(s), then the second option is what you're looking for. – Jason Apr 23 '14 at 20:26
  • If, instead, you just want the variable to be undefined until it's invoked by the first module that calls it, and thereafter it will be the same single variable available to everyone, then the first example does that. If you want something else, then I don't understand. – Jason Apr 23 '14 at 20:29
  • it's about specific naming - so I can't use a class to add these as properties because I need 'onlyToThisModule' to be 'onlyToThisModule' not 'mc.onlyToThisModule' or 'rats.onlyToThisModule' or 'hairball.onlyToThisModule' if that makes sense. – stockholmux Apr 23 '14 at 20:32
  • I assume you're saying that you need each calling module to have its own version of the variable allocated? If so, then you're stuck, it needs to be a class property if it needs to persist beyond the return of the function. If it doesn't need to persist beyond the end of the function, then just declare it inside the function and be done with it. If it's OK that each calling module access the same copy of the variable, then just declare it in your module scope but don't assign it until the function is called, it meets your requirement of being undefined prior to function call. – Jason Apr 23 '14 at 20:52
1

When you define a variable without using var it is put into global scope and has some other nuances beyond the scope of this comment. It is not an 'implicit var'.

Anyway, the module itself can be accessed from anywhere through require(), so you don't need to make anything global:

In aModule

exports.globalToEveryone = true;

In anotherModule

var globalVal = require('aModule').globalToEveryone;

To expand: The value you want to access from everywhere almost certainly should not be global if you want to control access to it. Probably a better answer for your purposes is to use an accessor method:

var globalToEveryone = undefined;
exports.globalToEveryone() = function() {
    if (globalToEveryone !== undefined) {
        return globalToEveryone;
    } else {
        // what happens if it hasn't been set yet?
    }
}

Elsewhere in your code when you are interested in the value, you would use

var localVersion = require('aModule').globalToEveryone();

You will find that although you have to type a little more to get to the value, you will have much more maintainable code.

Jerry
  • 3,391
  • 1
  • 19
  • 28
  • Doesn't exactly help my situation. So, I want to be able to have the variable name declared by the module itself only after the function `createAGlobal` is invoked. I should note, this is one variable of about 50 that I'm doing this way and the exactly names are pretty important. – stockholmux Apr 23 '14 at 20:18
  • I think the answer to your question lies in another question: What do you want to happen if another part of the code uses the variable before it is set? – Jerry Apr 23 '14 at 20:21
  • In that case the simple answer is to declare it as undefined until it is set. – Jerry Apr 23 '14 at 20:29
0

Alright. Thanks for the answer attempts, but after much annoyance and research, I don't think it is possible to do exactly what I was trying to do, but I got close enough for my project.

Dead ends: I was able to access the contents of variables up the scope chain using V8's stack trace and faking an error. I couldn't manage to write to the scope up the chain though. Plus that is somewhat insane. I also went down the route of trying to do some Object.defineProperty trickery, but I never could get that to work exactly as needed.

For my purpose, it works to put everything into an object then use with which is slightly less evil than globals. For the situation I need, it works fine. So, like this:

var local = { 
    a : '1',
    b : '2',
    c : '3'
};

function contained() {
    with (local) {
        console.log(a,b,c);
    }
}
console.log(typeof a) //undefined
contained();

Now I'll just pass the local object through the module and have the script or other module use with where needed. Not quite as syntactically pure as I would have liked. I also think that implicit variables should be local only to the module since you can access globals using the global.foo (I know, not going to happen, would break the internet and so on)

Community
  • 1
  • 1
stockholmux
  • 1,169
  • 11
  • 24