Global variables should be reserved for objects with system-wide dependencies, and their names should avoid ambiguity and minimize the risk of naming conflicts. In practice, this means that you should avoid creating global objects unless they are absolutely necessary.
But, well, you already knew this...
So what did you do about it? Traditional approaches tell us that the best strategy for eliminating globals is to create a small number of global objects that serve as actual namespaces for underlying modules and subsystems. I'll explore several approaches to namespaces and end with an elegant, safe, and flexible solution I came up with based on a recent article by James Edwards.
I use static namespace
as an umbrella term for those solutions where the namespace label is actually hard-coded. Yes, you can reassign one namespace to another, but the new namespace will reference the same objects as the old one.
The most basic method. This is very verbose, and if you want to rename these namespaces, you have work to do. But it's safe and clear.
var myApp = {} myApp.id = 0; myApp.next = function() { return myApp.id++; } myApp.reset = function() { myApp.id = 0; } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); //0, 1, undefined, 0
You can also make future maintenance easier by using this
to reference sibling properties, but this is a bit risky because there is nothing stopping your namespace methods from being redistribute.
var myApp = {} myApp.id = 0; myApp.next = function() { return this.id++; } myApp.reset = function() { this.id = 0; } myApp.next(); //0 myApp.next(); //1 var getNextId = myApp.next; getNextId(); //NaN whoops!
Now we only need to reference the namespace name once, so changing the name later is simpler (assuming you haven't referenced the namespace repeatedly). There is still a danger that the value of this
may throw up a "surprise" - but it is safer to assume that the object defined in an object literal structure will not be reallocated.
var myApp = { id: 0, next: function() { return this.id++; }, reset: function() { this.id = 0; } } window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
I find myself using Module Pattern
more lately. The logic is isolated from the global scope by a method wrapper (usually self-invoking), which returns an object representing the module's public interface. By immediately calling this method and assigning the result to a namespace variable, we lock the API of the module in this named variable. Additionally, any variables not included in the return value will always remain private, visible only to the public methods that reference them.
var myApp = (function() { var id= 0; return { next: function() { return id++; }, reset: function() { id = 0; } }; })(); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
As with the object literal example above, the namespace name can be easily changed, but there is an added advantage: object literals are class 4 - it's all about attribute assignment, no room for supporting logic. Furthermore, all properties must be initialized, and property values cannot be easily referenced across objects (so, for example, internal closures are not possible to use). Module pattern does not have any of the above constraints and gives us additional privacy benefits.
We can also call this section Namespace Injection
. The namespace is represented by a proxy that directly references the method wrapper inside of it - this means we no longer need to wrap the return value assigned to the namespace. This makes namespace definitions more flexible and allows having multiple independent instances of the module that exist in independent namespaces (or even in the global context). Dynamic namespaces support all the features of the module pattern with the added advantage of being intuitive and readable.
id is private because it is not assigned to
context.
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ) //0, 1, undefined, 0
context to the global object (with a single word change!). This is a huge asset for library owners - they can wrap their features in a self-calling function and let the user decide whether they are global or not (John Resig was an early adopter of this theory when he was writing JQuery) .
var myApp = {}; (function(context) { var id = 0; context.next = function() { return id++; }; context.reset = function() { id = 0; } })(this); window.console && console.log( next(), next(), reset(), next() ) //0, 1, undefined, 0
as a namespace proxy
this keyword (which is immutable within a given execution context), it cannot be accidentally modified.
var myApp = {}; (function() { var id = 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }).apply(myApp); window.console && console.log( myApp.next(), myApp.next(), myApp.reset(), myApp.next() ); //0, 1, undefined, 0
apply (and
call) API provides natural isolation from context and parameters – so passing additional parameters to the module creator is very clean . The following example illustrates this and shows how to run modules independently of multiple namespaces.
var subsys1 = {}, subsys2 = {}; var nextIdMod = function(startId) { var id = startId || 0; this.next = function() { return id++; }; this.reset = function() { id = 0; } }; nextIdMod.call(subsys1); nextIdMod.call(subsys2,1000); window.console && console.log( subsys1.next(), subsys1.next(), subsys2.next(), subsys1.reset(), subsys2.next(), subsys1.next() ) //0, 1, 1000, undefined, 1001, 0
nextIdMod(); window.console && console.log( next(), next(), reset(), next() ) //0, 1, undefined, 0
这个我们作为例子使用的 id 生成器工具并没有表现出这个模式的全部潜力。通过包裹一整个库和使用this
关键字作为命名空间的替身,我们使得用户在任何他们选择的上下文中运行这个库很轻松(包括全局上下文)。
//library code var protoQueryMooJo = function() { //everything } //user code var thirdParty = {}; protoQueryMooJo.apply(thirdParty);
我希望避免命名空间嵌套。它们很难追踪(对人和电脑都是)并且它们会让你的代码因为一些乱七八糟的东西变得很多。如 Peter Michaux 指出的,深度嵌套的命名空间可能是那些视图重新创建他们熟悉和热爱的长包链的老派 Java 开发者的遗产。
通过 .js 文件来固定一个单独的命名空间也是可以的(虽然只能通过命名空间注入或者直接分配每一个变量),不过你应该对依赖谨慎些。此外将命名空间绑定到文件上可以帮助读者更轻易弄清整个代码。
因为 JavaScript 并没有正式的命名空间结构,所以有很多自然形成的方法。这个调查只详细说明了其中的一部分,可能有更好的技术我没有发现。我很乐意知道它们。
以上就是JavaScript 中的命名空间详细介绍的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!