Update my JavaScript style! Avoiding global namespace pollution
-
In this job I'm doing a lot of JavaScript which I'm pretty rusty at.
The old way of reducing global namespace pollution was to do something like this:
if (typeof _myJS == 'undefined') { _myJS = new MyJS(); _myJS.init(); } function MyJS() { this.init = function () { // register handlers, set up timeouts, etc whatever _myJS.utilityFunction(fart); } this.utilityFunction = function() { // Maybe I get cookies, who knows } }
This works but has a few disadvantages:
- I'm not using "this" so the function-object refers to itself. I know some people will use var that = this; to establish a "this" that remains in the same scope. What I've usually done is use the function-object's global namespace name from within the function-object. This is obviously not ideal.
- It still uses one variable of global namespace. I know I could wrap this in parens to make it a "self-closure" (not sure that's the right term?) but if I do so, my setTimeouts have nothing to refer back to when they timeout.
It also has one big advantage:
- It works perfectly with Intellisense on any IDE I've used, whereas I'm not sure how well alternative syntax would work with my IDEs.
Would appreciate any comments from modern JS developers. Is this still a good/preferred way to solve the problem? If I put this in a code review, will the cool kidz on the point and laugh at me?
-
BTW if I remember right this is the method that Google Analytics' ga.js adopted, which I think is where I initially learned it. I'm not sure if GA is still written in this style.
-
@blakeyrat that's similar to how I still do, though i try to avoid the singleton-like style of calling itself through its own name (what happens when i need to change the name due to namespace issues!?) I do the
that = this
generally, or cheekily namedthat
s when I'm just goofing around.Though I usually declare it as
MyJS = function() { /* init and property declarations is here */ } MyJS.prototype = { /*methods here*/ } _myJS = new MyJS();
but i may be out of style too now.
-
That's about as good as you can do. Even with languages that have a formal concept of a namespace as its own entity, the names of the namespaces you use for collecting other entities end up in the global namespace.
-
@darkmatter said in Update my JavaScript style! Avoiding global namespace pollution:
I do the
that = this
generally, or cheekily namedthat
s when I'm just goofing around.I remember my CS professor in college joking that trying to explain the concept of
this
always reminded him of Bill Clinton talking about "what 'is' is."
-
Modern JS dev is almost always done with a build system, which would include either a node-style or ES6-style modules.
So you would then just
require()
orimport
your code around and never ever touch the global namespace. There wouldn't be any global scope pollution, since each module would have its own isolated namespace. Everything would happen within this closed environment.Since it doesn't seem like you're doing modules, this seems like a reasonable alternative.
var that = this
within your class is a good idea. I usually name itthisClassName
, to avoid confusion and help intellisense (eg.thisMyJS
).Don't use prototype for service class setup, you can't have hidden variables with that.
If I put this in a code review, will the cool kidz on the point and laugh at me?
Yes. But that's unrelated to the code.
-
@cartman82 I have no idea what anything in your first two statements means. I'm debating if I should bother finding out.
-
@fwd said in Update my JavaScript style! Avoiding global namespace pollution:
@cartman82 I have no idea what anything in your first two statements means. I'm debating if I should bother finding out.
You would write something like this.
import axios from 'axios'; import {getCookie} from './utils'; export class HTTPService { getUsers() { const cookie = getCookie(); return axios.get(cookoe).then(/*...*/); } }
You would put your code into
http_service.js
andutils.js
. There would be other files, all importing from each other (something else wouldimport HTTPService from 'http_service'
). There would be one entry point file, that imports and sets up this entire dependency graph.Then you setup a build system (gulp or webpack) and point it at that main file. The build system will go through everything, and produce something like
script.min.js
, which is all bunched together, converted to "legacy" javascript and minified. You would then load that file from your html.
-
@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
Then you setup a build system (gulp or webpack) and point it at that main file. The build system will go through everything, and produce something like script.min.js, which is all bunched together, converted to "legacy" javascript and minified.
Right; but how does script.min.js avoid namespace pollution? Or are you saying that gulp will put the entire script (plus dependencies) into a huge self-closure and wire it up to work with timeouts and event handlers?
I totally understand what you're saying about the development workflow, what I don't get is how exactly that solves the problem I stated in the first post.
-
Actually I realized I can answer my own question, because I just built a TypeScript project using Gulp.
It looks like this:
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.libname = global.libname || {}))); }(this, (function (exports) { /// code here })));
So yeah, it does put it in a big ol' closure, but it's also doing something with a
global.libname
I frankly don't understand.
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
Right; but how does script.min.js avoid namespace pollution? Or are you saying that gulp will put the entire script (plus dependencies) into a huge self-closure and wire it up to work with timeouts and event handlers?
Yes. Each module's code gets its own closure. Same goes for vendor libraries (as long as you use npm modules to install them). The only way modules communicate between themselves is through imports and exports. Never through global object.
I am not sure why you think there is a problem with "timeouts and error handlers". You can still freely use the
window
object from within this compiled code. CallsetInterval
andwindow.document
whenever you want, just don't attach anything to the global scope.
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
So yeah, it does put it in a big ol' closure, but it's also doing something with a global.libname I frankly don't understand.
Looks like global is window objects. And it will attach something for some reason.
Doesn't matter. Whatever it does, after a while you learn to trust the machine and live within this virtual world of ES6 and modules. The same way C++ programmers almost never touch the machine code if they can help it.
-
@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
I am not sure why you think there is a problem with "timeouts and error handlers".
Because if the object's in a closure, and the setTimeout is in the
window
ordocument
namespace, I would imagine they're not able to talk to each other, yes?I mean I'm obviously wrong, but I'm not wrapping my head around why.
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
Because if the object's in a closure, and the setTimeout is in the window.whatever namespace, I would imagine they're not able to talk to each other, yes?
function closure() { var window = { setTimeout: function () {} }; function myCode() { window.setTimeout(); } }
myCode
can talk with window object from the outside closure.Now imagine that there is a little bit of magic wheres you can automagically access anything on
window
. So you can just callsetTimeout()
.Why do you think this shouldn't work?
-
I'm not following.
@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
myCode can talk with window object from the outside closure.
Anybody can talk to
window
from anywhere in the DOM at any time. So... yes?@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
Now imagine that there is a little bit of magic wheres you can automagically access anything on window.
Again, that's not magic.
window
is a big global namespace that contains everything, so everybody can access it.@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
So you can just call setTimeout().
Of course you can call
setTimeout()
from inside the closure. My concern is once yoursetTimeout()
code runs, how can it access anything inside the closure? The closure has no name and is not addressable, AFAIK.@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
Why do you think this shouldn't work?
See above.
In my crap example, I can have my
setTimeout()
code refer to_myJS
and it can access whatever methods it needs there-- that's because_myJS
specifically is named in the global window namespace.If my library were in a big ol' closure, I wouldn't be able to refer to it from anywhere outside the closure, because (to my understanding) it has no name. It's not in
window
. MysetTimeout()
handler code can't refer to_myJS
because_myJS
isn't anywhere at all.Like I said above, I'm obviously wrong here. Just trying to wrap my head around how I'm wrong.
-
For example:
myjs.js
export function MyJS() { var thisMyJS = this; function action() { // your code } }
main.js
import {MyJS} from './myjs.js'; var myJS = new MyJS(); setInterval(function () { myJS.action(); }, 1000);
In this case,
main.js
would be something like a service container, which imports and initializes all other parts of your code.If you want to do it singleton style, you can also import/export variables.
myjs.js
function MyJS() { var thisMyJS = this; function action() { // your code } } export var myJS = new MyJS();
main.js
import {myJS} from './myjs.js'; setInterval(function () { myJS.action(); }, 1000);
-
Either nothing you just typed addresses my confusion at all, or you're so far above my head than I'm not processing it.
(Or you're just saying that the
import
psuedo-keyword puts shit in the global scope, but in a circular way.)
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
If my library were in a big ol' closure, I wouldn't be able to refer to it from anywhere outside the closure
That's where the magic of callbacks comes in. Your handler is a function inside the closure. The
window
code cannot normally access that handler, but by passing it the handler as a callback it gains access to it. The handler itself is inside your closure, so it has access to everything that anything else in your closure also has access to.
-
@blakeyrat can you post an example of what you want done with setInterval?
-
@pleegwat said in Update my JavaScript style! Avoiding global namespace pollution:
That's where the magic of callbacks comes in. Your handler is a function inside the closure.
So what you're saying is that I have to define my
setInterval
s like:(closure){ var that = this; this.setIntervalHand = function () { // do stuff in this function has access to that and can do shit with it } this.init() { setTimeout( this.setIntervalhand, 5000 ); } });
That works, and I get why. Because the setTimeout callback is keeping the closure "in scope" so the GC can't dispose of it. Fair enough.
But this:
(closure){ var that = this; this.setIntervalHand = function () { // do stuff in this function has access to that and can do shit with it } this.init() { setTimeout( function() { that.setIntervalHand() }, 5000 ); } });
Does not work because by the time setTimeout fires its anonymous function "that" is meaningless to it.
... am I following?
So it means I can still use
setTimeout
, but only if I'm creating thesetTimeout
handler function inside the closure and refer directly to it when I callsetTimeout
without using an anonymous function.JavaScript was confusing enough when it was goddamned straightforward. #GrumpyOldMan
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
Does not work because by the time setTimeout fires its anonymous function "that" is meaningless to it.
Incorrect. The anonymous fn that you give to setTimeout will keep reference to
that
. setTimeout does not figure into it.Just follow what is defined inside which curly brackets, like a pyramid. The anon function you give to setTimeout is 2 levels above
that
, but still in the same closure chain.Chrome debugger has a neat closure inspector in the right sidebar. Try setting up an example like this and pause inside your setTimeout handler. It will show you the full stack of captured closures underneath it.
-
@cartman82 said in Update my JavaScript style! Avoiding global namespace pollution:
Incorrect. The anonymous fn that you give to setTimeout will keep reference to that. setTimeout does not figure into it.
Ok, so the anonymous function's scope will be
window
, but the reference tothat
will still do the correct thing.Hm, I guess now that I think about it, I don't know why I thought it wouldn't. Oh well.
-
@blakeyrat setTimeout can work with lambdas, then anything inside it should work.
Maybe you're thinking in setTimeout with a string parameter? Then you would have the problem you describe.
Edit: didn't see there was another page of comments
-
@wharrgarbl said in Update my JavaScript style! Avoiding global namespace pollution:
Maybe you're thinking in setTimeout with a string parameter? Then you would have the problem you describe.
Possibly, or it was due to some bug in like IE 5.5 that's been sticking in my head for the last decade. Who knows. I started doing JavaScript dev a long time ago, which ironically probably puts me at a disadvantage against people who learned all this shit last week.
-
@blakeyrat
Never fear! In another week, you'll all be on even ground again!
-
@izzion Now just wait until I show you GWT and you start writing Java instead.
-
@blakeyrat said in Update my JavaScript style! Avoiding global namespace pollution:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.libname = global.libname || {})));
}(this, (function (exports) {
/// code here
})));If I include one of these AMD-compatible modules in my webpage's DOM, is there a way to simply call into it from raw JS on the page without having to set up the whole build pipeline? When I'm literally just calling
.start()
and nothing else?
-
Ok, I understand that you need to include require.JS on the page and give it a base directory to load scripts from, so after some dinking around on StackOverflow, I found a code example and created this:
// Adapted from: https://stackoverflow.com/questions/12697238/load-requirejs-module-inline-the-html-body function configureRequireJS() { var scripts = document.getElementsByTagName("script"); var src = scripts[scripts.length-1].src; var baseUrl = src.substring(src.indexOf(document.location.pathname), src.lastIndexOf('/')); require.config({ baseUrl: baseUrl, }); } configureRequireJS();
So I believe that gives me a configured and working (hopefully) requireJS. Now to just figure out how to load a specific module using it...
-
Ah I already had the hard stuff done. All that was left was:
var libConfig ={ }; require(['lib'], function (lib) { lib.start(libConfig); });