0

I'm looking for something that will import the contents of an object to the global scope:

var y = {}
y.x = 5

//do some magic here

console.log(x); //5

I want to do this is so I can make an easy to use module with memorable function names without having to worry about things accidentally getting overridden by other modules.

Consider this example:

funModule = {};

funModule.washClothes = function(clothes){...}
funModule.walkDog = function(dogName){...}
//etc

funModule.UNITED_STATES_DEFICIT = ...;

Here I've created a module that has some useful functions and constants (implementations and values were replaced with "...").

I don't want my users to have to type out the module name every time they call function or use a constant. That would result with really messy code:

funModule.walkDog(funModule.UNITED_STATES_DEFICIT);

I could try it again by defining everything globally:

washClothes = function(clothes){...}
walkDog = function(dogName){...}
//etc

UNITED_STATES_DEFICIT = ...;

but if a different module has also defined the commonly named function washClothes we've got trouble. (in my actual case the commonly named function is run)


Removed from technical context, here is the problem I'm faced with:

Firstly I want to use simple and memorable names to make the module easy to learn and fun to use.

Secondly I don't want the easy names to make the module impossible to use with others. Especially as it grows, a lot of common names will be used. It would be great if the users could decide whether or not import the names directly.

Thirdly I realized as I'm typing this that what I'm describing is something that definitely already exists, in python. See http://effbot.org/zone/import-confusion.htm for more detail.

tl;dr How can python-like imports be done with javascript?

EDIT:

It seems there is not a universal way to do this.

  • Using Window won't work in all environments (but will work in any common browser).
  • Apparently ES6 Modules are not available to web browsers directly.

This question is different from this one because its not about Node.js. I was looking for a universal way to do it, but that doesn't seem possible, so I'll limit it to web browsers, (namely chrome, firefox, safari, opera, and maybe ie)

EDIT:

This general article about Scope could be useful for anyone with a similar question as mine: https://toddmotto.com/everything-you-wanted-to-know-about-javascript-scope/

kboul
  • 13,836
  • 5
  • 42
  • 53
Samie Bencherif
  • 1,285
  • 12
  • 27
  • *"That would result with really messy code"* ... makes no sense. – charlietfl Dec 26 '16 at 19:54
  • @charlietfl Does it make more sense if replace "messy" with "repetitive"? – Samie Bencherif Dec 26 '16 at 19:56
  • Yes...when you are polluting global namespace. What's wrong with a shortened alias? Then it is also more clear where the method is actually coming frrom – charlietfl Dec 26 '16 at 19:57
  • 1
    "*without having to worry about things accidentally getting overridden by other modules*" - then WTH did you choose to import them to the global scope?! – Bergi Dec 26 '16 at 20:02
  • @Bergi I know it's strange, but it's a big enough priority to have users able to opt out of having to retype the alias so often that it's worth considering. – Samie Bencherif Dec 26 '16 at 20:11
  • @charlietfl A shorter alias still makes the code less clear if it is repeated very often. Readability is a big concern for me. – Samie Bencherif Dec 26 '16 at 20:12
  • Well that is very much subject to opinion – charlietfl Dec 26 '16 at 20:16
  • @charlietfl That's right. And thus we enter the subjective realm of programming. Even if we don't have the same opinions, it can totally still be useful to figure out ways to adjust code to make it more readable or anything else that could be important. – Samie Bencherif Dec 26 '16 at 20:29
  • Well polluting global namespace is not a subjective issue – charlietfl Dec 26 '16 at 20:32
  • @charlietfl Could you post your concerns as answer, just so it's more clear than a chain of comments. As mentioned in the question, adding things to the global namespace would need to be an opt-in feature. – Samie Bencherif Dec 26 '16 at 20:37

4 Answers4

1

ES6 Modules are what you want. If you will define your object as es6 module you could do this (using the names in your example):

import { washClothes } from "fun-module";

and then washClothes will be globally available on the file that imported it, just like you want.

Read about it here.

If you really want a magic solution like in the comment in your post and don't want to use ES6 and you run in the browser you can put it on the window object:

window.x = 5
Eran Shabi
  • 14,201
  • 7
  • 30
  • 51
1

In JavaScript, at least in a browser, global variables are properties of the window object: that is, window.x and x (where x is global) reference the same value. So, in theory, you could use Object.assign() to copy your object's properties to the window object making them global variables. This is roughly equivalent to globals().update(myobj.__dict__) in Python.

But just as import * is usually a bad idea in Python, so too this sounds like a bad idea, except even worse because window has a lot of other properties that you probably don't want to clobber.

kindall
  • 178,883
  • 35
  • 278
  • 309
1
 Object.prototype.makeglobal=function(){   
    for(key in this){
      if(window[key]){//replace window if youre not in a browser
      //already exist, error handling
      console.error(key+' already exist in window');
   }else{
      window[key]=this[key];
   }}};

Use like this:

funModule.makeglobal();
//now you can
washClothes();

But this is bad as it pollutes the global object.

2.Your user should create his own namespace:

function(){
 this.washClothes();
//more of his content
}.call(funModule);

3.You could also add a loader:

funModule.load=function(func){
    console.log(func); 
    console.log(this);
    func.call(this,this);
};

Now you can do:

funModule.load(function(fun){
 this.washClothes();
 fun.washClothes();
 });

4.If youre concerned about readability you may use function chaining (?):

funModule.washClothes=function(){
  //your code
   return this;
 } 

now you can do:

funModule.washClothes("tshirts").washClothes("trousers").washClothes();
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Not in all environments is `window` the global object – Bergi Dec 26 '16 at 20:04
  • Could you help me understand #3? – Samie Bencherif Dec 26 '16 at 20:32
  • 1
    ()=>{} is an arrow function, same as a normal function. So you call funModule.load(mycode) and the js does then : mycode.call(this,this). This is funModule in that case. Call calls the function and overrides this with the first argument, the rest is passed. So the first argument of mycode will be funModule, and this will be funModule... – Jonas Wilms Dec 26 '16 at 20:35
0

After some additional research I found a way, without polluting the global namespace, to allow users to directly access module contents.

This solution allows the user to:

  1. Write code that directly references the module's functions/properties
  2. Define precedence if there are multiple modules written in this same style
  3. Still access the module's functions/properties by module name*

*This feature comes with a catch

Here's the code


Module

funModule = {};

//This stuff is the arbitrary contents of the module:

funModule.washClothes = function(clothes){...}
funModule.walkDog = function(dogName){...}
//etc

funModule.UNITED_STATES_DEFICIT = ...;
//etc

//This part is necessary:

funModule.run(userApp)
{

  for(key in this){
    eval(key + " = " + this[key] + ";");
  }

  userApp();

}

The only way (that I could find) to dynamically define functions both in funModule.run's scope and in funModule is to use Eval. Using call, apply, or bind to manipulate scope would still require use of the this keyword and the whole point of this unusual style is to make client code as simple and non-repetitive as possible.

Client Code 1

function myApp()
{
    washClothes(UNITED_STATES_DEFICIT);
}
funModule.run(myApp);

Here in the client code it is possible to directly access everything except for funModule.run. So the global namespace is kept clean but the user's code does not need unnecessary repetition.

Client Code 2

function myApp()
{
    washClothes(UNITED_STATES_DEFICIT);
}
funModule.run( otherModule.run.bind({},myApp) );  //otherModule has precedence here

Assume otherModule is a different module that features the same run function. funModule will load its contents then call its first argument. The first argument will load otherModule's contents, overriding anything from funModule with the same name.

Client Code 3

function myApp()
{
    //directly access stuff from funModule
    walkDog()
    var big = UNITED_STATES_DEFICIT * 3.14;

    //explicitly access stuff from specific modules
    clothes = new otherModule.Clothes();
    funModule.washClothes(otherModule.washClothes(clothes));
}
funModule.run(myApp)

This is the feature that makes use of eval necessary. The user can opt out of ambiguity of direct access. They can still access properties/methods by naming the module they come from.

But Why?

Some StackOverflow users were understandably concerned about the unusual set of constraints in the question, so I figured I would answer the following question:

Why don't you use a short alias for your module.

I tried to answer that question in this article, which pulls from this question and answer.

Samie Bencherif
  • 1,285
  • 12
  • 27