Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exported variables are read only?

Tags:

typescript

globals.ts:

export let TimeZone: number = 0;

app.js

import * as globals from "./globals";
globals.TimeZone = -5;

The last line gives:

error TS2540: Cannot assign to 'TimeZone' because it is a read-only property.

Why?

like image 593
Old Geezer Avatar asked Dec 04 '18 16:12

Old Geezer


1 Answers

Imports are a read-only view of the exported binding in the source module's environment. Even if the source binding is mutable (as in your example), you can't use the imported view of it to modify it. Only the module exporting it can modify it.

Why? Because one module importing the variable shouldn't be able to reach into the source module and change the variable's value. If the source module wants to make it possible for modules using it to change the value of the exported variable, it can expose a function to do that. (Or expose an object with mutable properties.)

Remember that modules are shared across the various modules importing them. So if Modules A and B both import Module C, you don't want Module A modifying what Module B sees (even if Module C can, because it's a variable, not a constant).

FWIW, here's an example (live copy on plunker):

index.html:

<!DOCTYPE html>
<html>

  <head>
  </head>

  <body>
    <p id="display">0</p>
    <script type="module" src="imp1.js"></script>
    <script type="module" src="imp2.js"></script>
  </body>

</html>

counter.js:

export let counter = 0;

export function increment() {
  ++counter;
}

imp1.js:

import { counter } from "./counter.js";

const display = document.getElementById("display");

setInterval(() => {
  // This module sees changes counter.js makes to the counter
  display.textContent = counter;
}, 100);

imp2.js:

import { counter, increment } from "./counter.js";

// Trying to set it directly fails
try {
  counter = 127;
} catch (e) {
  document.body.insertAdjacentHTML(
    "beforeend",
    "imp2: Failed to directly set counter to 127"
  );
}

setInterval(() => {
  // But it can ask counter.js to do it
  increment();
}, 200);

I should note that although you can't modify the binding (TimeZone in your example), if you export an object, that object's state can be modified. For example:

// In the exporting module
export const container = {
    TimeZone: 0,
};

// In the importing module
import { container } from "./module.js";
container.TimeZone = 42;

That works, and the change is seen by any code that also imports container. Sometimes you want to defend against that, which you might do with Object.freeze (if the source module doesn't need to ever change the properties of the object) or a Proxy.

But again, changing an object's state is not the same as changing the value of the binding that was exported.

like image 145
T.J. Crowder Avatar answered Nov 03 '22 08:11

T.J. Crowder