sotiristherobot
sotiristherobot

Reputation: 299

Deeper understanding of modules mechanisms in Javascript

I'm looking a little bit deeper inside ES6 modules and I've noticed the following which I've found interesting and I'd like to clear it out.

Observation a. All modules in JavaScript are singletons by default, so

// module a.js
let notification = 10;
export default notification;

Assuming we have a module b.js and c.js, and we import there a.js notification will be shared. Notification will be readonly.

Observation b. If a function is exported from module that returns an object then a new object is created.

// module a.js
let notification = 10;
export default () => ({
 notification
})

//somewhere in module b.js
import Fn from 'a.js'
let n = Fn().notification; // a new number is created since numbers are immutable in javascript but is this the reason why notification from a.js stays the same?
n = n + 10; // outputs 20

//somewhere in module c.js
import Fn from 'a.js'
let n = Fn().notification; // outputs 10

Based on my understanding, this happens because a new object is created every time?

Observation c. If we want to share the value in a module we need to follow the following pattern?

   //module a.js
    let notification = 10;
    export default () => ({
     notification,
     setNotification() {
       notification += 10;
     }
    })

If the setNotification is called in one of the modules that it's imported, then automatically then the value of notification will be 20 everywhere else the module a is imported.

Could someone shed some light on why the above is happening, and if my observations are correct?

Upvotes: 1

Views: 108

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075019

Based on my understanding, this happens because a new object is created every time?

That's not why it happens, but yes, a new object is created every time. But the number doesn't change because you haven't changed it. You've just changed your local n variable, which is completely unconnected to the notification property of the object returned by Fn.

If we want to share the value in a module we need to follow the following pattern?

That will work, but you don't have to do it that way. This works just as well:

export default {
    notification: 10
};

In another module:

import obj from "./a.js";
console.log(obj.notification); // 10
obj.notification = 20;
console.log(obj.notification); // 20

If after the code above you have a third module import and use it, they'll see 20:

import obj from "./a.js";
console.log(obj.notification); // 20

Stepping back from the mechanics of it, though, in general it's probably not best practice to modify objects you receive that way, and in fact you might even consider freezing objects you return from a module (or not returning objects other than functions at all) so you prevent odd cross-talk between the modules using your module's export.

Here's another example you may find enlightening: Although the binding you import is read-only, it's a live binding to the exporting module's local binding, which the local module can change. So this works:

source.js:

export let notification = 10;
export function setNotification(n) {
    notification = n;
};

a.js:

import { notification, setNotification } from "./source.js";
console.log(notification); // 10
setNotification(20);

b.js:

import { notification, setNotification } from "./source.js";
console.log(notification); // 20

main.js:

import "./a.js";
import "./b.js";

You can think of an imported binding as a really efficient getter (accessor) for the binding in the exporting module.

Note that the order in which a.js and b.js run, and thus the values they see, is determined by the order in which main.js imported from them (and also by the fact they don't have any circular references or dynamic import in them).

Upvotes: 2

Related Questions