0

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.

1
  • You have defined a to be an object. Object contains key-value pair. So, try writing a={b: 'test'} in your first example. Commented Feb 27, 2017 at 4:38

2 Answers 2

2

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'
Sign up to request clarification or add additional context in comments.

Comments

2

foo can modify the variable a to point to something else.

But this has no effect on the object exported. When the require is done, the calling module receives whatever a pointed to at the time. After that, it does not care about what happens to (the variable) a.

In your second example, you are not assigning a to a new object, but you are modifying the existing object (by adding a new field). That will of course be visible to anyone who got hold of that object before.

This is (very roughly) analogous to

function callerDoesNotSeeThis(a){  a = 1 }

function callerSeesThis(a){  a.foo = 1 }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.