Here's my plea to all JavaScript developers, whether for web pages, Firefox extensions, or otherwise: Please respect the global namespace.
I've not seen a lot written about this, but as fast as new uses of JavaScript are appearing on web pages, and with how many extensions are being developed for Firefox, function and variable naming collisions and conflicts are a real potential issue.
In a web page, all functions and variables share the same global namespace - that of the Window
object.
In a Firefox extension, all declared functions and variables become properties of the ChromeWindow
.
(Note that in JavaScript, a function is basically just another variable.
function someFunction(…){…}
and var someFunction = function(…){…};
are identical, and someFunction
can be passed and reassigned just like someString
from var someString = "Hello, World!"
.
As such, the same issue pertains to both functions and other variables.)
(Don't) prefix your variable names
One primitive approach to preventing problems is to prefix variable names, as discussed at http://forums.mozillazine.org/viewtopic.php?t=129485. While this may slightly help to lessen the chance of naming collisions, it is not the best solution. There is no system in place to prevent 2 authors or code libraries from choosing the "abc_" prefix. The best alternative to such an approach would be to choose a prefix that is more guaranteed to be unique, such as a utilizing an owned Internet domain name, such as with Java packages. For example, I might prefix all my JavaScript code with "com_ziesemer_", to separate it from other JavaScript libraries I may utilize either now or in the future. But there are still better options!
JavaScript is an object-oriented language
Contrary to some popular beliefs, JavaScript is very much an object-oriented language. As such, I feel that an object-oriented solution is in order.
One of the best posts I've found about this is from Douglas Crockford, a senior JavaScript Architect at Yahoo!, and well known for introducing JavaScript Object Notation(JSON). In "Global Domination" on the Yahoo! User Interface Blog, "Global variables are evil". His post is later referred to by Eric Miraglia, also of Yahoo!, in "A JavaScript Module Pattern", including a useful code example.
Another posting I found, "Namspacing your JavaScript", I'm listing more for the comments that follow than the original post. (Choosing "DED" as a top-level variable name is a prime example of choosing a bad name.)
To clearly state the desired trend, most of the best examples and methods make use of JSON and/or JavaScript closures.
Package it up
Further following the appearance of Java packages, I recommend something like the following, again, utilizing JavaScript as an OO language:
if(!com) var com={}; if(!com.ziesemer) com.ziesemer={}; com.ziesemer.myPackage = { name: 'visitor', location: 'someplace unknown', alertHello: function(){ alert('Hello, ' + this.name + ", from " + this.location + '!'); } }
(See JavaScript Closures for more context.)
The if
checks at the top prevent for wiping out the objects if they already exist, and allow for either creating each level as a new object, or extending an existing object at that level.
Update (2008-05-12): See my JavaScript namespace function for a function to handle all of this in one call, e.g. namespace("com.ziesemer.myPackage").alertHello = { /* … */ };
Update (2009-12-02): See my proposal to add the above namespace function to the base ECMAScript / JavaScript language.
I am also honored to see that this page itself has been linked to from the Mozilla Firefox Add-on Review Process.
The other advantage of this design over prefixing is that each package can easily be aliased, e.g.:
var czmp = com.ziesemer.myPackage; czmp.name = 'Mark'; czmp.location = 'Wausau, WI'; czmp.alertHello();
Note that such use should only be done from within another function or another scope limitation, otherwise "czmp" would become a member of the global namespace, which is exactly what this is trying to prevent.
An alternative would be to use the JavaScript with
statement:
(However, this is only meant as a possibility, not a recommendation. I would nest the above in a closure function instead. See Douglas Crockford's "with Statement Considered Harmful" for details.)
with(com.ziesemer.myPackage){ name = 'Mark'; location = 'Wausau, WI'; alertHello(); }
Checking Firefox and extensions
To see how populated/polluted your Firefox global namespace is, follow the instructions at JavaScript debugging in Firefox extensions to setup the JavaScript Debugger/Venkman to inspect the browser window. Then run the following two commands at the debugger's Interactive Session window to get an idea of all the variables created at the global level:
var vars=[]; for(var v in this){vars.push(v);} vars.sort(); vars.length;
On my current Firefox install with less than 20 extensions installed, this yields more than 1,500 variables!
(439 of them begin with "webdeveloper_
" from the Web Developer extension.)
Update (2009-12-02): See Mats Bryntse's "Global is the new private". It shows the number of global variables used by 30+ popular JavaScript frameworks, from YUI (currently 1), to Microsoft VirtualEarth (currently 880!). It also allows for analysis of any given script (available somewhere online). If that wasn't enough, it even calculates namespace collisions between chosen frameworks.
Update (2008-12-19):
This is in response to Arturo's comment. I'm replying here so that I can properly format my code.
I'm not sure how you got "hello content". Given your code above, I'd expect "hello undefined".
I think avoiding the JSON syntax would probably be the best bet with what you're attempting to do.
Also, beware that your example declares "com.ziesemer.myPackage" twice, which is unnecessary.
Here's what I would recommend, using closures:
if(!com) var com={}; if(!com.ziesemer) com.ziesemer={}; com.ziesemer.myPackage = function(){ var pub = {}; pub.name = "visitor"; pub.greeting = "hello " + pub.name; pub.alertHello = function(){ alert(pub.greeting); }; return pub; }(); com.ziesemer.myPackage.alertHello();
You can tweak this to not assign everything to "pub
", essentially making some things private.
You could also somewhat combine both methods, utilizing the "with
" within the closure - though that could be somewhat confusing.
BTW, please don't use "com.ziesemer" - that namespace is registered to me. :-)
32 comments:
Great summary!
BTW, the "with" keyword has been deprecated in FF 1.5? Is there any other way to achieve the same result?
Max -
I'm surprised you're still using 1.5, considering FF 2.0 and now 3.0 has been released, and that 1.5 is no longer supported. :-)
Actually, according to http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Statements/with#Description, the deprecation warning was a bug (#322430) particular to 1.5 only, and was fixed in 1.5.0.1 and above.
I like the buildup of the package-type namespace. I'm going to use that going forward. Thanks for the great post.
Weird Issue here:
What if I wanted something like this:
if(!com) var com={};
if(!com.ziesemer) com.ziesemer={};
if(!com.ziesemer.myPackage) com.ziesemer.myPackage={};
com.ziesemer.myPackage = {
name: 'visitor',
greeting: 'hello ' + this.name,
alertHello: function(){
alert(this.greeting);
}
}
If I run com.ziesemer.myPackage.alertHello() I get 'hello content', instead of the expected 'hello visitor'.
Why is that??
Arturu - please see my response in the update to the post.
Hey ;)
Thanks for that clarification!
Don't worry, I was just using the same namespace for the example published here ;)
Thanks again!
Hi Mark,
I am using the code you recommended for Firefox extension, as below:
if(!com) var com={};
if(!com.ziesemer) com.ziesemer={};
com.ziesemer.myPackage = function(){};
and I include this .js file in the main XUL file, and then it generates an error in the error console:
invalid assignment left-hand side:
com.ziesemer.myPackage = function(){};
is there any solutions to it?
Thanks for a good reference.
As a novice javascript programmer and extension developer I have a question. How is the problem with loose variables exploited (or even used as a positive side effect)?
As far as I know, It is not sufficient to make a variabel global in the overlay file in order to access it from a javascript loaded in the firefox sidebar (instead I have to do something like this https://developer.mozilla.org/en/Working_with_windows_in_chrome_code#Accessing_the_elements_of_the_top-level_document_from_a_child_window)
So then, how can other extensions access my lose variables if I cannot access them directly myself from separate javascript files in my own extension?
Best regards,
Claudijo
Ryan - I don't know, especially without seeing more details. Just check for typos, etc. The code is definitely correct.
Claudijo - I'd have to say that this and the extensions windows are two different issues. The main goal behind this post is simply managing the namespace of a given scope. Your issue with the different extension windows is working across different scopes.
By "namspacing", we're just lessening the possibility for naming collisions. It doesn't change any possibility for exploits. I also certainly don't see any positive side effect to having more of the loose variables.
The code you provided:
if(!com) var com={};
if(!com.ziesemer) com.ziesemer={};
if(!com.ziesemer.myPackage) com.ziesemer.myPackage={};
with(com.ziesemer.myPackage){
name = "visitor";
greeting = "hello " + name;
alertHello = function(){
alert(greeting);
}
}
com.ziesemer.myPackage.alertHello();
throws an error when executing on firefox 3.01: com.ziesemer.myPackage.alertHello() is not a function!
The second solution worked well.
Anonymous - I guess that is why I didn't recommend it. I'm removing it from the post as it is incorrect.
I like this technique. However I am wondering, how would you use your method to encapsulate 3 javascript source files, but still have them as the same "package"?
I'm sure I could do it by adding another "sub domain" to the dot-notation, but what if I want them all to have the same "domain"?
Vinny2020 - it really doesn't matter. Whether it is a "sub-domain" or a function within a "domain", they are both accessed through the same dot-notation. So you could start out by declaring one "package" with various sub-packages and functions in your first source file. In each additional source file, you could add additional sub-packages and/or functions.
Mark, yes that will work, but in the additional javascript files how would I define the function? Would I have to use the whole package name? If I redefine the initial "package" function in file 2, that will blow away anything created in file 1. So after the initial definition in file 1, do I then use the full "namespace" to define functions in file 2"?
Using your example, in file 2 would I then use the following:
com.ziesemer.myPackage.newFunction = function(){};
I am just wondering, do I need to fully qualify each function in the subsequent javascript files.
Vinny2020 - First, we should use something like my JavaScript namespace function to declare the namespaces so that each level is only added when necessary, without overwriting anything.
Using this, assume in the first file:
"com.example".namespace().a = function(){...};
"com.example".namespace().b = function(){...};
In a second file, etc., we can continue to extend "com.example":
"com.example".namespace().c = function(){...};
"com.example.a".namespace().aa = function(){...};
Thanks, Mark. I believe I understand now. We can set up the base of the namespace in file1 and then extend the namespace in file2 or even add more functions to the original namespace. I like this method. Great job. Thanks for your assistance.
I wrote an article regarding the importance of namespacing in JavaScript.
Hi,
I got an email from mozilla addons that I need to wrap my variables and functions in the way you suggest. However it does not seem to work because the "this" variable is not pointing to the object I defined but to a chrome window. See the example below
What should I do to correct this?
function debug (str) {
consoleService = Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
consoleService.logStringMessage("DictionarySearch: " + str);
}
if(!org) var org={};
if(!org.mozdev) org.mozdev={};
if(!org.mozdev.dictionarysearch) org.mozdev.dictionarysearch={};
org.mozdev.dictionarysearch = {
_member_variable: true,
init: function () {
debug (this); // this is a ChromeWindow object here
// this._member_variable is undefined here.
}
};
// Every time a new browser window is made init will be called
window.addEventListener("load",org.mozdev.dictionarysearch.init,false);
Jaap - have you tried removing "this.", and just referencing "_member_variable"?
If there is a naming-conflict with another variable in a function closer in the scope chain, you could access the inner variable as fully-qualified, e.g. org.mozdev.dictionarysearch._member_variable.
I'd also recommend writing the object as a function with a closure, rather than using JSON.
Hey Mark,
Thanks for the article!
I am currently re-writing an extension of mine to have it moved from the sandbox. I had a couple of functions in my JS file doing certain tasks and now I tried doing the following:
if(!com) var com={};
if(!com.hottypo) com.hottypo={};
if(!com.hottypo.myPackage) com.hottypo.myPackage={};
com.hottypo.myPackage = {
function1: function(event, type)
{
var something = "ss";
}
function2: function(event, type)
{
var something2 = "ssgfddgfa";
}
}
And then in my XML file I was calling the various functions as follows:
com.hottypo.myPackage.function1()
When installing the addon to try it out I receive the following error in the console:
Error: com is not defined
Source File: chrome://browser/content/browser.xul
Line: 1
What exactly am i doing wrong?
Thanks!
Rez - it's difficult to provide a complete answer without being able to view the complete extension, but it sounds like you're working with 2 separate scopes (possibly different windows or frames), or that your .js file is not being loaded. If you use the steps and debugging tools listed at https://developer.mozilla.org/en/Building_an_Extension, I'd guess you should be able to get a handle on what's happening.
hello everyone !!
I'm like a beginner in java Script and restructuring one of my Firefox addon for global Namespacing.
------------------
var namespace = new function() {
return {
i: 0,
var: 'bar',
function1: function(){
alert("inside function 1");
namespace.function2();},
function2: function() {
var j = 0;
alert("inside function2");}
};
}
---------------
when i invoke "namespace.function1()" in XUL file, i get this error in Error Console.
"Error: namespace is not defined
Source file: chrome://browser/content/browser.xul
Line: 1 "
Venkman also is not proving to be useful for me, or may i'm not able to make right use of it. Please guide me... Mark ?? or Any1 out there ??
if(!com) var com={};
if(!com.ziesemer) com.ziesemer={};
com.ziesemer.myPackage = function(){
var pub = {};
pub.name = "visitor";
pub.greeting = "hello " + pub.name;
pub.alertHello = function(){
alert(pub.greeting);
};
return pub;
}();
com.ziesemer.myfun = function()
{
var tst = {};
tst.msg = "This is another funtion";
alert(tst.msg);
}
I wanted to call "myfunc" from myPackage function. How can I do that? can anyone help?
Ravi: com.ziesemer.myfun(); Will work from anywhere, including from within any other function.
Vikram - the code you posted by itself is valid, though I assume that you would replace "namespace" with something more unique.
See my response to Rez, just above your comment, as it looks like you two are having the exact same issue.
Hi Mark, great article! I wonder if it is still valid with ECMAScript 1.6 implemention "Mozilla Rhino" 1.6 release 2.
I cut and pasted the updated sample and I get the following error running it inside jrunscript:
[ste ~] o(= jrunscript /tmp/test.js
script error in file /tmp/test.js : sun.org.mozilla.javascript.EcmaError: TypeError: Cannot call property alertHello in object [JavaPackage com.ziesemer.myPackage]. It is not a function, it is "object". (/tmp/test.js#14) in /tmp/test.js at line number 14
Any advise?
Ste: I have the same problem.
The problem seems to be Rhino pre-defines java, com, org, net, and edu as Java packages and assumes any reference relative to them to be also a Java Package.
Thus com, com.ziesemer, com.ziesemer.myPackage all evaluate as JavaPackage instead of undefined so none of the first three lines get effectively executed.
It should work if you force the assignments like:
com = {};
com.ziesemer = {};
com.ziesemer.myPackage = {};
but it may mess up other things if you want to use the Java package later. Not cool.
Doesn't using 'let' rather than 'var' in Javascript 1.7 within extensions avoid polluting the global namespace in an easier manner? see: https://developer.mozilla.org/en/new_in_javascript_1.7
Ste / gigamorph:
Regarding the "JavaPackage" issues under Mozilla Rhino, please refer to https://bugzilla.mozilla.org/show_bug.cgi?id=468385.
As a quick work-around, one can just evaluate "delete" statements during the Rhino initialization to remove these object references, e.g. "delete com;delete net;delete org;delete edu".
Nick: You're completely correct - using the "let" statement introduced with JavaScript 1.7 is a great alternative. This should work great for use within Firefox extensions, but for use within web pages, the only web browser currently supporting JavaScript 1.7 is also Mozilla Firefox.
However, the object namespacing approach still serves a purpose, even when "let" is available. Namespacing essentially keeps the variables still accessible - but more "organized", and prevents naming collisions through a standard naming structure.
Hi Mark,
I do have a concern with global namespace and javascript modules.
I'm using your rules for my add-on, but now I want to isolate part of my code in a javascript module.
So I have all my javascript code wrapped into the following object for extension QuickFileMail :
if(!net) var net={};
if(!net.phpconcept) net.phpconcept={};
if(!net.phpconcept.xul) net.phpconcept.xul={};
if(!net.phpconcept.xul.quick_file_mail) net.phpconcept.xul.quick_file_mail={ [...My code...] };
But now I want to have part of my code in Javascript Modules :
https://developer.mozilla.org/en/JavaScript/Code_modules/Using
So I would like to make a module of the code wrapped in net.phpconcept.xul.quick_file_mail.classifier = {}
I can not find a working way to export a module by respecting your rule. Using a dedicated namespace for the module is not working if I'm using "var net={}" because the module does not use the same namespace.
It seems I can export only single object name (and not the full object name with "dots"), which mean I have to break the namespace rule and find a "uniq string name" for my module (like net_phpconcept_xul_quick_file_mail_classifier) which might not be uniq.
Any good idea for me ? Any way to have a hierarchie of modules or something like that ?
very nice post. thanks a ton..
Post a Comment