views:

619

answers:

4

I want to be able to package my JavaScript code into a 'namespace' to prevent name clashes with other libraries. Since the declaration of a namespace should be a simple piece of code I don't want to depend on any external libraries to provide me with this functionality. I've found various pieces of advice on how to do this simply but none seem to be free of errors when run through JSLint (using 'The Good Parts' options).

As an example, I tried this from Advanced JavaScript (section Namespaces without YUI):

"use strict";
if (typeof(MyNamespace) === 'undefined') {
    MyNamespace = {};
}

Running this through JSLint gives the following errors:

Problem at line 2 character 12: 'MyNamespace' is not defined.
Problem at line 3 character 5: 'MyNamespace' is not defined.
Implied global: MyNamespace 2,3

The 'Implied global' error can be fixed by explicitly declaring MyNamespace...

"use strict";
if (typeof(MyNamespace) === 'undefined') {
    var MyNamespace = {};
}

...and the other two errors can be fixed by declaring the variable outside the if block.

"use strict";
var MyNamespace;
if (typeof(MyNamespace) === 'undefined') {
    MyNamespace = {};
}

So that works, but it seems to me that (since MyNamespace will always be undefined at the point it is checked?) it is equivalent to the much simpler:

"use strict";
var MyNamespace = {};

JSLint is content with this but I'm concerned that I've simplified the code to such an extent that it will no longer function correctly as a namespace. Is this final formulation sensible?

+1  A: 

Surely the point of the undefined check is to ensure that users are not double-loading or overwriting an existing namespace. As such, I think you've gone too far.

How about:

var MyNs;
if(MyNs==null){
    //foo()
}
spender
JSLint suggests using `===` rather than `==` (using 'The Good Parts' options) but with that and whitespace changes it is happy with this from a syntax point of view.
Matthew Murdoch
-1: checking against `null` is conceptually wrong and only works because of the evil-ness of `==`
Christoph
Christoph: But == null can identify nulls and undefined. +1 to the question
M28
+2  A: 

You can access global variables through the window["NAMEOFGLOBAL"] syntax, so you could do your check like this instead:

if(typeof(window['MyNamespace']) === 'undefined') {
Amber
I guess this assumes that the javascript code will be run as part of a web page (if the code is used within a firefox extension, for example, I don't think this will work).
Matthew Murdoch
Correct; `window` would generally be specific to browsers.
Amber
`this` normally refers to `window` in the global browser scope. Would `if(typeof(this['MyNamespace']) === 'undefined') {` work in places `window` could not be assumed?
dave1010
+1  A: 

I've encountered this issue before. You definitely don't want to use your final form, as it is completely bypassing the check for a defined variable and will always overwrite it to an empty object, even if things were already stored in it.

I think your second to last form is the most appropriate for creating a namespace, but really the first form is decent as well. The errors that JSLint reports with it are sort of catch-22s in my opinion and not much to worry about. The reason I think it's not a big deal to leave off the declaration is that, in the case where the namespace is loaded at some previous point, you'd end up with JSLint's "variable was used before it was defined" error, so you're essentially just trading one warning for another.

Jimmy Cuadra
Could you explain a bit more about why the errors are a 'catch-22'?
Matthew Murdoch
What benefit is the check for a defined variable giving - is it there just to prevent the 'double-loading' scenario highlighted by @spender?
Matthew Murdoch
Edited my answer to better explain why the warning JSLint gives you should be taken with a grain of salt. And yes, the point of the undefined check is to prevent accidentally overwriting a variable that's been used previously. In a simple application it may not ever come up, but when things start to get more complex and your code is spread across multiple files, it's a useful safeguard.
Jimmy Cuadra
+1, but can JSLint pick up cases where the namespace is loaded twice(i.e. can it be used to review multiple .js files loaded together by an HTML file)?
Matthew Murdoch
To the best of my knowledge, you can run HTML through it, but it will just be looking at the code inside `script` tags and inline event handlers – it won't load external scripts and execute them in order in the way a browser does.
Jimmy Cuadra
+4  A: 

Don't take JSLint's word as gospel. Much of what it says is sensible, but it also comes with a lot of Crockford's personal dogma attached. In particular I don't always agree with him about the best place for var.

"use strict";
if (typeof(MyNamespace) === 'undefined') {
    MyNamespace = {};
}

That one's no good; JSLint is correct to complain about the implied global. 'use strict' requires you don't imply globals.

"use strict";
if (typeof(MyNamespace) === 'undefined') {
    var MyNamespace = {};
}

That's fine. The var is hoisted so MyNamespace is present and set to undefined on entering the code block. So you could do that test as (MyNamespace===undefined) even without the typeof operator's magic ability to let you refer to variables that don't exist.

The other way is to use the unambiguous in operator (which is the only way to distinguish between an absent property one that is present but set to undefined). For the case of globals in a normal browser script, you can use it against the global window object:

'use strict';
if (!('MyNamespace' in window)) {
    window.MyNamespace = {};
}

(JSLint doesn't like that either, as ‘assume a browser’ doesn't seem to define window for some unfathomable reason. Hey ho.)

bobince
+1 - Nice answer! So does the formulation with `var MyNamespace;` outside of the if block (which gives no JSLint errors) have any semantic problems or is that also OK?
Matthew Murdoch
It's OK, as long as we are indeed talking global code. If you declare a global `var` that already has a value, nothing happens (it's not reset to `undefined`). Whereas if you declare a local `var` with the same name as a defined global, the local variable with value `undefined` hides the global.
bobince