Search code examples
javascriptnode.jsfunctionglobal-variablesvar

Node.js: accidentally omitted var declaration expose all variables via global object, bug or feature?


Fairly new to NodeJs (however not to Javascript) and I think it is great, not in all aspects but that is not the subject of the question.

Found this 'feature' by accident, actually it is exactly what I want however don't know it is legit or a bug. What I did in an include file:

// var <- var is now a comment
 ksNone         = 0,
 ksAltKey       = 1, 
 ksCtrlKey      = 2,
 ksShiftKey     = 4; 
 ......
 .......

When traversing the global object in the main file, with code below ...

code:

require( './lib/mycode.js' );

for( var p in global )
{ console.log( p ); }

.... you will finally see:

output:

........ <- many other vars
 ........
 ........
ksNone
ksAltKey
ksCtrlKey
ksShiftKey

Made me thinking, it not easy to include a file with a bunch of functions for general purpose. For example, I have some functions to validate strings and numbers and all kind of other stuff that doesn't need to be in a namespace or class. Normally, to include such functions you have to specify exports for it or include it in a hacky way via fs and eval() - see also this question.

I tried the the following:

code:

 ksNone         = 0,
 ksAltKey       = 1, 
 ksCtrlKey      = 2,
 ksShiftKey     = 4,
 isNumber = function( i ) 
 {
   return typeof i === 'number' && isFinite(i) && !isNaN(i);
 },
 isValidNumber = function( i, iMin, iMax )
 {
  if( !isNumber( i ) )
   { return false; }

  if( isNumber( iMin ) && i < iMin ) 
   { return false; }

  if( isNumber( iMax ) && i > iMax ) 
   { return false; }

  return true;  
 }, 
isString = function( a ) 
 {
   return ( typeof a === 'string' || ( a instanceof String ));
 }, 
 isValidString = function( s, iMinLen, iMaxLen )
 {
   if( isString( s ) )
   {
     var iLen   = s.length,
         bIsMin = ( isNumber( iMinLen ) && iMinLen >= 0 )?(iLen >= iMinLen):(iLen > 0),
         bIsMax = ( isNumber( iMaxLen ) && iMaxLen >= 0 )?(iLen <= iMaxLen):(iLen > 0);

     return ( bIsMin && bIsMax );    
   }

   return false;
 };

And traversing again will now output:

output:

 ........ <- many other vars
 ........
 ........
ksNone
ksAltKey
ksCtrlKey
ksShiftKey
isNumber
isValidNumber
isString
isValidString

Once included, because it is now in the global scope, I can do this everywhere:

code:

var test = "yes"

if( isValidString( test ) ) // call the global function
 { console.log( "String is valid" ); }

output:

String is valid

questions:

Normally it is not a good idea to left out declaration stuff such as var (it doesn't work in strict mode - "use strict"; ) however in this case it seems to be very handy because you don't need to specify exports or use a hacky way to include it and those functions are globally accessable without a namespace or const/var require declaration.

What actually happen when you include a file? Scans the NodeJs core consts, vars, functions to keep it private? Is it legit to use or is it a bug? A do or don't, what do you think?


Solution

  • Normally it is not a good idea to left out declaration stuff such as var (it doesn't work in strict mode - "use strict"; ) however in this case it seems to be very handy because you don't need to specify exports or use a hacky way to include it and those functions are globally accessable without a namespace or const/var require declaration.

    The node.js module system encourages:

    1. Enhanced ability to reuse or share modules
    2. Well defined interface between modules, done the same way by everyone and every module
    3. Enhanced testability of modules
    4. No global symbol conflicts
    5. Code privacy within module scope (harder to hack or unintentionally mess with things)
    6. Explicit dependencies between modules

    Globals can cause various problems:

    1. Accidental naming conflicts in the global namespace
    2. Implicit dependencies (module requires some unstated global condition to exist before it works properly)
    3. Things can be overwritten or hacked or just generally messed with in ways the author does not intend

    The only downside of doing it the proper export/import way is a slight bit more typing to import or export interfaces between modules.

    So, here are my recommendations related to your question:

    1. Do not use globals for interfacing between modules. Just get used to a slight bit more typing to properly import and export things. It will pay off in the short and long run.
    2. Run your code in strict mode, thus requiring that all variables be explicitly declared and no ability to have an accidental or implicit global. These are accidents just waiting to happen. Avoid them. Declare all variables.
    3. Use let or const to specify/limit the scope of your variable, not even var.

    What actually happen when you include a file? Scans the NodeJs core consts, vars, functions to keep it private? Is it legit to use or is it a bug? A do or don't, what do you think?

    Implicit globals don't get created until the line of code that first assigns to them is executed. There's no advanced scanning. They get created upon demand. But, don't use that - don't rely on it. This is not a bug. It is a legacy Javascript design decision (most would consider it a bad design decision) that was done on purpose. But, avoid it. Don't program that way on purpose and use strict mode so that the interpreter will point out any accidental variables that you forgot to declare.

    Inside a function, all var declared variables are created at the time the function is called. This is called variable hoisting" and is a feature of the language. When the interpreter compiles the function, it makes a note of all var declarations within the function so that when it runs that function, it can pre-create them each time the function starts running.

    let and const variables are only available within the block scope they are created. var is function scoped, let and const are blocked scoped. Unlike var, you cannot reference a let or const variable within the scope until the line of code containing its definition has run (a further safety over strict mode).