Triet Dang Triet Dang -4 years ago 100
Javascript Question

Weird behavior with variable scope and 'require' in NodeJS

I have 2 files:

testrequire.js

let a = {};

function foo() {
a = 'test';
};

module.exports.foo = foo;
module.exports.a = a;


test.js

let a = require('./testrequire');

a.foo();

console.log(a);


When I run
test.js
, this is the result:

{ foo: [Function: foo], a: {} }


But I expect it to be like this:

{ foo: [Fuction: foo], a: 'test' }





However, when I change testrequire.js like this:

let a = {};

function foo() {
a.b = 'test';
};

module.exports.foo = foo;
module.exports.a = a;


The result is:

{ foo: [Function: foo], a: { b: 'test' } }


And it is perfectly like what I expected.




The question here is: Why function
foo()
can modify
a
's properties while it cannot modify
a
?

P/S: I did try
var
instead of
let
and the result is still the same. So it is definitely not ES6
let
fault.

Answer Source

It's a pointer thing. It's the same in C/C++, Java etc. We've gotten so used to closures that we've sort of expect regular pointers to work the same. But pointers/references are simple indirections.

Let's walk through your code:

let a = {};

Create an object ({}) and point the variable a to that object.

function foo() {
    a = 'test';
};

Declare a function foo() that overwrites the value of a with a string. Now, if you remember your C/assembly then you'd remember that the value of a pointer is the address of the thing it points to. So the original value of a is not {} but the address to that object. When you overwrite a with a string that object still exist and can be garbage collected unless something else points to it.

module.exports.foo = foo;
module.exports.a = a;

Export two properties, 1. foo which points to a function and 2. a which points to the same object that a is pointing to. Remember, just like in C/Java this does not mean that module.exports.a points to a but that it points to {}. Now you have two variables pointing to the same object {}.

Now, when you do:

a.foo();

All you're doing is changing the enclosed variable a to point to a string instead of the original object. You haven't done anything to a.a at all. It's still pointing to {}.


Workarounds

There are two ways to get what you want. First, the OO way. Don't create a closure for a, make it a regular object property:

function foo() {
  this.a = 'test';
};
module.exports.foo = foo;
module.exports.a = {};

This will work as expected because modules in node.js are proper singletons so they can be treated as regular objects.

The second way to do this to use a getter to get the enclosed a. Remember that closures only work with functions, not objects. So just assigning the variable to a property like you did results in a regular pointer operation not a closure. The workaround is this:

let a = {};

function foo() {
  a = 'test';
};
function getA() {
  return a; // this works because we've created a closure
}
module.exports.foo = foo;
module.exports.getA = getA;

Now you can do:

a.foo();
a.getA(); // should return 'test'
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download