DEV Community

How to avoid namespace pollution in Javascript

Eckehard on August 09, 2021

I found this brilliant post about namespace pollution, which notes that - beside of conflicting name definitions - using global variables may have ...
Collapse
 
artydev profile image
artydev • Edited

Hy Eckehard,

Look at this book online from Addy Osmani

Especialy the Revealing Pattern : RP

Regards

var myRevealingModule = (function () {

        var privateVar = "Ben Cherry",
            publicVar = "Hey there!";

        function privateFunction() {
            console.log( "Name:" + privateVar );
        }

        function publicSetName( strName ) {
            privateVar = strName;
        }

        function publicGetName() {
            privateFunction();
        }


        // Reveal public pointers to
        // private functions and properties

        return {
            setName: publicSetName,
            greeting: publicVar,
            getName: publicGetName
        };

    })();

myRevealingModule.setName( "Paul Kinlan" );
Enter fullscreen mode Exit fullscreen mode
Collapse
 
efpage profile image
Eckehard

Thank you for the hints.

I think we can achieve the same result using classes, which has the advantage that you can use setters and getters.

I just wanted to avoid the module name on every function call. Maybe in Javascript, the only option are named imports.

Collapse
 
peerreynders profile image
peerreynders

I think we can achieve the same result using classes

The entire idea behind the revealing module pattern is to keep the "private" parts truly "private" inside a function closure created via an intermediately invoked function expression (IIFE). JavaScript always had function closures.

OOP with Functions in JavaScript

Private class fields are only part of the Class field declarations for JavaScript and private methods are part of the Private methods and getter/setters for JavaScript classes TC39 proposals which are currently at Stage 3 - which means they may become part of ES2023 if they go to Stage 4 before then.

Babel right now includes @babel/plugin-proposal-class-prope... and @babel/plugin-proposal-private-met... by default in @babel/preset-env

Can I Use: JavaScript classes: Private class fields

Can I Use: JavaScript classes: Private class methods

MDN: Private Class Features

which has the advantage that you can use setters and getters.

In the example given the object returned includes the getName() accessor and the setName() mutator. But perhaps you were referring to the getter and setter object property syntax:

const myModule = (() => {
  let name = 'Ben Cherry';

  return {
    get name() {
      return name;
    },
    set name(value) {
      name = value;
    },
  };
})();

console.log(myModule.name); // "Ben Cherry"
myModule.name = 'Paul Kinlan';
console.log(myModule.name); // "Paul Kinlan"
Enter fullscreen mode Exit fullscreen mode

It just works!

Collapse
 
artydev profile image
artydev • Edited

Hy Eckehard

In a module you cant import whatever variables from another one, and use them without any prefix.

TodoListTutor

Feel free to correct :-)

Regards

PS : Have you considered turning DML UI Elment in customeElements ?

TodoListCE

Thread Thread
 
efpage profile image
Eckehard

No, we had a focus on long term compatibility (expect IE), CE had a limited support some years ago.

I try to keep modules as small as possible. But maybe somebody could write a wrapper as a separate module? On the other hand, what's the advantage?

Thread Thread
 
artydev profile image
artydev • Edited

For the CE , it was in fact to illustrate,how easy it was to create one.
You can create a lib containing only DML-CE like Shoelace for CSS

But if IE is a concerned, you can look at a article I post WE
which allows create CE without any polyfill that works even in IE.

Regards

Collapse
 
peerreynders profile image
peerreynders • Edited

Your app can use libB.myVariable or libA.myVariable or myVariable, if no conflict occured - Simple solution

In Javascript, name clashes cannot be solved this way.

Using named imports of modules is not a similar elegant solution.

// file: libA.js
const myVariable = 42;

export {
  myVariable
};
Enter fullscreen mode Exit fullscreen mode
// file: libB.js
const myVariable = 9007199254740881;

export {
  myVariable
};
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Avoiding Namespace Pollution</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the console</p>
    <script type="module">
      import * as libA from './libA.js';
      import * as libB from './libB.js';

      console.assert(libA.myVariable === 42);
      console.assert(libB.myVariable === 9007199254740881);
      console.log('done');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Are the extra * as characters really worth fretting about?

Collapse
 
efpage profile image
Eckehard

No, but this forces to use the libary name on any function or variable of any library you use. The C++-solution is far more handy, only to use a qualifier where necessary.

Collapse
 
peerreynders profile image
peerreynders
// file: libA.js
const myVariable = 42;
const someVariable = 10;

export {
  myVariable,
  someVariable,
};
Enter fullscreen mode Exit fullscreen mode
// file: libB.js
const myVariable = 9007199254740881;
const anotherVariable = 1000;

export {
  myVariable,
  anotherVariable
}
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Avoiding Namespace Pollution</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the console</p>
    <script type="module">
     import * as libA from './libA.js';
     import * as libB from './libB.js';
     const { someVariable} = libA;
     const { anotherVariable} = libB;

     console.assert(libA.myVariable === 42);
     console.assert(libB.myVariable === 9007199254740881);
     console.assert(someVariable === 10);
     console.assert(anotherVariable === 1000);

     console.log('done');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The C++-solution is far more handy, only to use a qualifier where necessary.

Douglas Crockford, JavaScript - The Good Parts, 2008; p.2:

JavaScript is most despised because it isn’t some other language. If you are good in some other language and you have to program in an environment that only supports JavaScript, then you are forced to use JavaScript, and that is annoying. Most people in that situation don’t even bother to learn JavaScript first, and then they are surprised when JavaScript turns out to have significant differences from the some other language they would rather be using, and that those differences matter.

Thread Thread
 
efpage profile image
Eckehard

2008 is long ago. Javascript has made a good progress over the last years and gained a lot of strength (and speed). I still think the designers made some strange decisions, but overall I´m not unhappy. The lack of strong typing makes less trouble than I expected and gives you some flexibility, that other languages miss.

I think it is absolutely OK to compare Javascript with other languages. Lot´s of things HAVE been changed already, but for some things we need to find tricks and solutions to make better use of it. That´s the reason we are talking about.

Thread Thread
 
peerreynders profile image
peerreynders

2008 is long ago.

The quote is timeless because people constantly try to find their language inside of JavaScript and only end up perpetually creating grief for themselves. The message of the book was to let go of any preconceived notion from other programming languages (and paradigms) and to simply deal with JavaScript on its own terms. That advice is as valid today as it was back then.

It's natural for people to stick to the familiar but when it comes to programming languages outside of the OOP, imperative bubble (C, C++, C#, Java, Python etc.) it's time get a fresh start.

JavaScript uses a familiar C-style syntax but Brendan Eich had Scheme on the brain when he designed it - and it shows to this day (Yes, JavaScript is a Lisp).

With TypeScript Microsoft (being Microsoft) tried to gloss over that fact:

TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the programmers at Microsoft could bring traditional object-oriented programs to the web.

… but as they were actually trying to type (most) JavaScript code they had to go far beyond class and interface.

In fact the introduction of class in ES2015 was controversial as it was seen as a means to make JavaScript more palatable to programmers coming from mainstream OOP languages - especially give that even in 1994 GoF recommended (p.20):

Favor object composition over class inheritance.

Also JavaScript was already truly object-oriented given that code could always construct objects even without facilities such as classes or even constructor functions i.e .it was never class-based.

And as the MDN states:

Classes are a template for creating objects.

In JavaScript objects can be augmented - so an object's class-membership isn't permanent for life. And in ES5 factory functions were in common use for creating objects.

These days classes have become an important part of "the platform" - given that the original Web API was already largely object-based, classes became the mechanism by which natively implemented facilities are now extended with JavaScript.

But to some degree this has more to do with "the browser as an environment that can be automated with JavaScript" rather than "JavaScript the Programming Language" - even from the very beginning people have conflated (the "messy") DOM API (a part of the browser) with JavaScript (the programming language).


Javascript has made a good progress over the last years and gained a lot of strength (and speed).

Yes, V8 is phenomenally fast but JavaScript is still an interpreted language and none of the "progress" has substantially changed the underlying character of JavaScript. JavaScript simply can't take advantage of the tricks that most compiled languages can.


I still think the designers made some strange decisions, but overall I´m not unhappy.

Whenever you run into a "strange decision" have a look at ES Discuss and TC39/proposal to see what went into making that decision - you may be surprised at the wide range of perspectives that were considered - not that it makes the decision any less weird in retrospect.


I think it is absolutely OK to compare Javascript with other languages.

On the server-side that makes sense because you can switch when you can see the balance tipping. I still regularly 🤦‍♂️ when I discover that some organization chose to use JavaScript on the server when there are clearly other, better options. The whole universal, isomorphic argument seems a bit self-serving - though in some cases for small teams it can make sense (Marko).

But when it comes to the browser there is no real alternative other than perhaps transpiling to JavaScript - so that conversation is really going nowhere.

WebAssembly:

It is also designed to run alongside JavaScript, allowing both to work together.

If you really wanted to you could use C++ Compiling a New C/C++ Module to WebAssembly (I'd rather go with Rust) - but is that always an effective solution?

And languages that require separate multi-megabyte run times aren't going to succeed outside of niche applications - JavaScript doesn't have to ship a separate runtime and Web APIs are all designed with JavaScript in mind.

On top of everything else, by necessity software running in the browser is subject to very different design constraints than software running somewhere in a cloud server data centre. Given The Mobile Performance Inequality Gap, 2021 the cavalier attitude of creating a product by indiscriminately aggregating many packages from npm may be tolerable for some server side work but is absolutely the wrong direction for client side products.

"We need this to run to on a low powered embedded system - no, wait we're not allowed to ship binary code - we can only send JavaScript and the deployed engine will already be claiming most of the system's resources."

… is probably a more appropriate perspective for approaching client-side product design.


Lot´s of things HAVE been changed already, but for some things we need to find tricks and solutions to make better use of it.

Perhaps rather than stating something like:

I'm used to doing X this way

ask something like

How exactly does one solve problem Y in the browser (with JavaScript)?

The XY Problem

Thread Thread
 
efpage profile image
Eckehard • Edited

Sorry, but your comments are a bit off topic. At least, now I know why some people are called tech-evangelists - they cannot stop praying. You do not even know what task I want so solve, so how do you know, you have the right tools?

Please, stay on topic. There your help is much appreciated.

Collapse
 
efpage profile image
Eckehard

What about imports inside a module? This pollutes the namespace of the module, but not the global namespace:

html
<- module A <- libA
<- module B <- libB

I assume that dynamic import would also help to get short loading times in some cases.

Collapse
 
peerreynders profile image
peerreynders
  • I'm not sure what you are asking here - I was under the impression you were concerned about name collisions when using multiple wildcard imports in the same module.
  • "Shorter loading" times simply means that HTML and CSS loading and processing won't get blocked by JavaScript loading/parsing/executing; i.e. to have a benefit the page will have to contain static HTML and CSS.
Collapse
 
efpage profile image
Eckehard

This was a questions about the scope of the global namespace inside a module. I assume, something like window[myVariable] = "NewValue" is not possible in a module?!?

Thread Thread
 
peerreynders profile image
peerreynders • Edited
  • It's only top level var and function declarations in inline JavaScript that pollute the global object (typically prevented with an IIFE). This doesn't happen in type="module" or ECMAScript modules - those names are "module global" but do not create properties on the global object.
  • const and let are block scoped so they don't affect the global object.
  • In modules you can still alter the global object via any name that is bound to it (provided that part isn't read-only). So window['myVariable'] = 'NewValue'; is possible.
  • Imported names are "module global" but do not create properties on the global object

<!DOCTYPE html>
<!-- index.html -->
<html>
  <head>
    <title>Top level Variables</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <p>Check the (web) developer tools console</p>
    <script type="module">
      // global names:
      //
      // `window` - browser window only
      // `self` - browser window & web worker (including service worker)
      // `global` - Node.js only
      // `globalThis` - browser window & web worker & Node.js

      console.log('TYPE: inline module - defer by default');

      // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
      console.assert(globalThis.hasOwnProperty('varModVariable') === false);
      console.assert(typeof varModVariable === 'undefined');
      var varModVariable = 'varModValue';
      console.assert(globalThis.hasOwnProperty('varModVariable') === false);
      console.assert(varModVariable === 'varModValue');
      console.assert(typeof globalThis.varModVariable === 'undefined');
      console.assert(typeof self.varModVariable === 'undefined');
      console.assert(typeof window.varModVariable === 'undefined');

      // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      const constVariable = 'constValue';
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      console.assert(constVariable === 'constValue');

      // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      let letVariable = 'letValue';
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      console.assert(letVariable === 'letValue'); // "letValue"

      console.assert(globalThis.hasOwnProperty('myModGlobal') === false);
      globalThis.myModGlobal = 'globalModValue';
      console.assert(globalThis.hasOwnProperty('myModGlobal') === true);
      console.assert(globalThis['myModGlobal'] === 'globalModValue');
      console.assert(globalThis.myModGlobal === 'globalModValue');
      console.assert(self.myModGlobal === 'globalModValue');
      console.assert(window.myModGlobal === 'globalModValue');
      console.assert(myModGlobal === 'globalModValue');
    </script>
    <script>
      (function () {
        console.log(
          'TYPE: inline text/javascript + immediately invoked function expression (IIFE)'
        );
        // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
        console.assert(globalThis.hasOwnProperty('varIifeVariable') === false);
        console.assert(typeof varIifeVariable === 'undefined');
        var varIifeVariable = 'varIifeValue';
        console.assert(globalThis.hasOwnProperty('varIifeVariable') === false);
        console.assert(varIifeVariable === 'varIifeValue');
        console.assert(typeof globalThis.varIifeVariable === 'undefined');
        console.assert(typeof self.varIifeVariable === 'undefined');
        console.assert(typeof window.varIifeVariable === 'undefined');

        // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
        console.assert(globalThis.hasOwnProperty('constVariable') === false);
        const constVariable = 'constValue';
        console.assert(globalThis.hasOwnProperty('constVariable') === false);
        console.assert(constVariable === 'constValue');

        // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
        console.assert(globalThis.hasOwnProperty('letVariable') === false);
        let letVariable = 'letValue';
        console.assert(globalThis.hasOwnProperty('letVariable') === false);
        console.assert(letVariable === 'letValue'); // "letValue"

        console.assert(globalThis.hasOwnProperty('myIifeGlobal') === false);
        globalThis.myIifeGlobal = 'globalIifeValue';
        console.assert(globalThis.hasOwnProperty('myIifeGlobal') === true);
        console.assert(globalThis['myIifeGlobal'] === 'globalIifeValue');
        console.assert(globalThis.myIifeGlobal === 'globalIifeValue');
        console.assert(self.myIifeGlobal === 'globalIifeValue');
        console.assert(window.myIifeGlobal === 'globalIifeValue');
        console.assert(myIifeGlobal === 'globalIifeValue');
      })();
    </script>
    <script>
      console.log('TYPE: inline text/javascript');

      // console.log(noVariable); // Uncaught ReferenceError: noVariable is not defined
      console.assert(globalThis.hasOwnProperty('varVariable') === true);
      console.assert(typeof varVariable === 'undefined');
      var varVariable = 'varValue';
      console.assert(globalThis.hasOwnProperty('varVariable') === true);
      console.assert(varVariable === 'varValue');
      console.assert(globalThis.varVariable === 'varValue');
      console.assert(self.varVariable === 'varValue');
      console.assert(window.varVariable === 'varValue');

      // console.log(constVariable); // Uncaught ReferenceError: Cannot access 'constVariable' before initialization
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      const constVariable = 'constValue';
      console.assert(globalThis.hasOwnProperty('constVariable') === false);
      console.assert(constVariable === 'constValue');

      // console.log(letVariable); // Uncaught ReferenceError: Cannot access 'letVariable' before initialization
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      let letVariable = 'letValue';
      console.assert(globalThis.hasOwnProperty('letVariable') === false);
      console.assert(letVariable === 'letValue'); // "letValue"

      console.assert(globalThis.hasOwnProperty('myGlobal') === false);
      globalThis.myGlobal = 'globalValue';
      console.assert(globalThis.hasOwnProperty('myGlobal') === true);
      console.assert(globalThis['myGlobal'] === 'globalValue');
      console.assert(globalThis.myGlobal === 'globalValue');
      console.assert(self.myGlobal === 'globalValue');
      console.assert(window.myGlobal === 'globalValue');
      console.assert(myGlobal === 'globalValue');
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode