[About modularization and why modularization]
Let's first explain why we need to be modularized. In fact, this is still related to the coding idea and the convenience of code management (the problem of namespace pollution is not mentioned because I believe that programmers who have already considered modular ideas should have at least one set of their own naming rules, in small and medium-sized sites, the probability of namespace pollution is very small, but it does not mean that it does not exist. This problem will be discussed later ).
In fact, the idea of modularization is exactly the same as the idea of object-oriented, but the so-called "module" in our mouth may be a bigger object than the so-called "object. We combine functions dedicated to achieving the same purpose through good encapsulation and ensure their good reusability, we can probably call this idea of combining code snippets an object-oriented idea. There are many advantages such as ease of use, versatility, maintainability, readability, and avoidance of variable name pollution.
Modularization is nothing more than object-oriented modules. We combine functions related to the same project (module) and manage them by a common name. It can be said that the idea is modular. Therefore, compared with object-oriented, I think it is easier to implement the modular idea in the Code architecture than object-oriented implementation.
Unlike c #, java, and other strong-type languages with good modularity and namespace mechanisms. JavaScript does not provide any language functions for creating and managing modules. Because of this, when we code JavaScript, the use of the so-called namespace may seem too casual (including myself ). For example:Copy codeCode: var Hongru ={} // namespace
(Function (){
Hongru. Class1 = function (){
// TODO
}
...
Hongru. Class2 = function (){
// TODO
}
})();
As mentioned above, we usually use a global variable or global object as our namespace, which is so simple that it may even seem a little casual to assume such a significant responsibility. But can we say this is not good? No, but I think everyone who has this encoding habit should be worthy of praise...
Therefore, it is enough to simply use this method to do the namespace work when we are working on some projects or building some websites of small scale. There is basically no big mess. But back to the essence, if there is code cleanliness or building a large-scale website, or at the beginning, we will adopt an absolutely elegant attitude and logic for code architecture. Maybe we should consider better namespace registration and management methods.
In this regard, jQuery is slightly inferior to YUI, Mootool, EXT, etc. (although jq also has its own modular mechanism ), however, this still does not affect our love for it. After all, the focus is different. jq is strong in its selector, otherwise it will not be called j-Query.
Therefore, it is reasonable to say that jQuery is suitable for small and medium-sized websites. Just like the Do framework of the open-source front-end Lightweight Framework of Douban, it is also built on jQuery, which encapsulates a modular management idea and the function of simultaneous file loading.
[About namespace]
Now, let's get back to the question. The above method, simply using a global object as the namespace can effectively reduce global variables and avoid variable name pollution, but once the website grows, or when there are many projects, there are still problems with managing the namespace of multiple global objects. If a name conflict occurs, one module overwrites the attributes of the other module, and neither of them works properly. In addition, it is quite difficult to identify problems. Therefore, we may need a mechanism or tool to determine whether there are duplicate names when creating a namespace.
On the other hand, different modules, that is, different namespaces, cannot be completely independent. Sometimes we also need to create the same method or attribute in different namespaces, at this time, the import and export of methods or properties will also be a problem.
I thought about the above two aspects and did some tests, but there were still some mistakes. Today, I flipped through the "Rhino Book" again, which is indeed a classic. the above problem is easily solved. Based on the solution and demo of the "Rhino Book", I made some modifications and simplification. Share your understanding. The following are important points:
-- Test the availability of each sub-module
Because our namespace is an object and has the hierarchical relationship that the object should have, when detecting the availability of the namespace, We need to judge and register based on this hierarchical relationship, this is especially important when registering a sub-namespace. For example, if we register a new namespace named Hongru, We need to register another namespace named Hongru. me, that is, our intention is that me namespace is the sub-namespace of Hongru. They should have a parent-child relationship. Therefore, when registering a namespace, you must use '.' to split it and make corresponding judgments one by one. Therefore, the code for registering a namespace is roughly as follows:
Copy codeThe Code is as follows: // create namespace --> return a top namespace
Module. createNamespace = function (name, version ){
If (! Name) throw new Error ('name required ');
If (name. charAt (0) = '. '| name. charAt (name. length-1) = '. '| name. indexOf ('.. ')! =-1) throw new Error ('illegal name ');
Var parts = name. split ('.');
Var container = Module. globalNamespace;
For (var I = 0; I <parts. length; I ++ ){
Var part = parts [I];
If (! Container [part]) container [part] = {};
Container = container [part];
}
Var namespace = container;
If (namespace. NAME) throw new Error ('module "'+ name +'" is already defined ');
Namespace. NAME = name;
If (version) namespace. VERSION = version;
Module. modules [name] = namespace;
Return namespace;
};
Note: The Module above is a general Module for registering and managing namespace. As a "base Module", it has a Module queue attribute of modules, it is used to store our newly registered namespace. With this queue, we can easily determine that the namespace has been registered:
Copy codeThe Code is as follows: var Module;
// Check Module --> make sure 'module' is not existed
If (!! Module & (typeof Module! = 'Object' | Module. NAME) throw new Error ("NameSpace 'module' already Exists! ");
Module = {};
Module. NAME = 'module ';
Module. Versions = 0.1;
Module. EXPORT = ['require ',
'Importsymbols'];
Module. EXPORT_ OK = ['createnamespace ',
'Isdefined ',
'Modules ',
'Globalnamespace'];
Module. globalNamespace = this;
Module. modules = {'module': Module };
The last line of the code above is a namespace queue. All new namespaces will be placed in it. With the previous code, we can manage our namespace very well. As for the Module, the "base Module" also has some EXPORT and other attributes, we will continue to talk about it later. The following is a namespace creation test demo.
Copy codeThe Code is as follows: Module. createNamespace ('hongru', 0.1); // register a namespace named Hongru, version 0.1
The second version parameter can also be used, if you do not need a version number. Under chrome-debugger, we can see the newly registered namespace.
We can see that the newly registered Hongru namespace has taken effect: Check the Module queue again:
We can see that the newly registered Hongru is also added to the Module's modules queue. We also found that there are several methods in the Module: require, isDefined, and importSymbols.
Because require (detection version) and isDefined (registered during namespace detection) are not difficult, just a little simpler:
-- Version and duplicate name Detection
Copy codeThe Code is as follows: // check name is defined or not
Module. isDefined = function (name ){
Return name in Module. modules;
};
// Check version
Module. require = function (name, version ){
If (! (Name in Module. modules) throw new Error ('module '+ name +' is not defined ');
If (! Version) return;
Var n = Module. modules [name];
If (! N. VERSION | n. VERSION <version) throw new Error ('version' + version + 'or greater is required ');
};
The above two methods are very simple. I believe everyone understands that one is to check whether the queue is named again, and the other is to check whether the version has reached the required version. There is nothing special, so I will not elaborate on it. What is a little more complicated is the import of attributes or methods between namespaces.
-- Export of attributes or methods marked in the namespace
Because we want a common namespace registration and management tool, we need to consider the configurability when marking the import or export. We cannot import or export all of them in one brain. So we can see the EXPORT and EXPORT_ OK arrays in the Module template as the tag queue for storing the attributes or methods that we can EXPORT. EXPORT is a public tag queue, and EXPORT_ OK is a tag queue that we can customize. If you do not think this is clear, you can use only one tag queue, used to store the tag attributes or methods you can export.
With the tag queue, the EXPORT operation is only for the tags in the tag queue of EXPORT and EXPORT_ OK.Copy codeThe Code is as follows: // import module
Module. importSymbols = function (from ){
If (typeof form = 'string') from = Module. modules [from];
Var to = Module. globalNamespace; // dafault
Var symbols = [];
Var firstsymbol = 1;
If (arguments. length> 1 & typeof arguments [1] = 'object' & arguments [1]! = Null ){
To = arguments [1];
Firstsymbol = 2;
}
For (var a = firstsymbol; a <arguments. length; a ++ ){
Symbols. push (arguments [a]);
}
If (symbols. length = 0 ){
// Default export list
If (from. EXPORT ){
For (var I = 0; I <from. EXPORT. length; I ++ ){
Var s = from. EXPORT [I];
To [s] = from [s];
}
Return;
} Else if (! From. EXPORT_ OK ){
// EXPORT array & EXPORT_ OK array both undefined
For (var s in from ){
To [s] = from [s];
Return;
}
}
}
If (symbols. length> 0 ){
Var allowed;
If (from. EXPORT | form. EXPORT_ OK ){
Allowed = {};
If (from. EXPORT ){
For (var I = 0; I <form. EXPORT. length; I ++ ){
Allowed [from. EXPORT [I] = true;
}
}
If (from. EXPORT_ OK ){
For (var I = 0; I <form. EXPORT_ OK .length; I ++ ){
Allowed [form. EXPORT_ OK [I] = true;
}
}
}
}
// Import the symbols
For (var I = 0; I <symbols. length; I ++ ){
Var s = symbols [I];
If (! (S in from) throw new Error ('symbol '+ s +' is not defined ');
If (!! Allowed &&! (S in allowed) throw new Error (s + 'is not public, cannot be imported ');
To [s] = form [s];
}
}
In this method, the first parameter is the exported source space, and the second parameter is the imported destination space (optional, globalNamespace defined by default). The following parameters are optional, the specific attributes or methods you want to export are all marked in the queue by default.
The following is a test demo:Copy codeThe Code is as follows: Module. createNamespace ('hongru ');
Module. createNamespace ('me', 0.1 );
Me. EXPORT = ['describe']
Me. define = function (){
This. NAME = '_ me ';
}
Module. importSymbols (me, Hongru); // import the mark in the me namespace to the Hongru namespace
You can see the test results:
The define () method defined in the me namespace is imported to the Hongru namespace. Of course, the import and export mentioned here is actually just copy. The define () method can still be accessed and used in the me namespace.
Well, let's talk about it here. This demo only provides a way of managing namespaces. There must be more perfect methods. You can refer to the YUI, EXT, and other frameworks. Or refer to the module and namespace section in the JavaScript authoritative guide.
Finally, paste the source code:
Copy codeThe Code is as follows:/* = Module and NameSpace tool-func =
* Author: hongru. chen
* Date: 2010-12-05
*/
Var Module;
// Check Module --> make sure 'module' is not existed
If (!! Module & (typeof Module! = 'Object' | Module. NAME) throw new Error ("NameSpace 'module' already Exists! ");
Module = {};
Module. NAME = 'module ';
Module. Versions = 0.1;
Module. EXPORT = ['require ',
'Importsymbols'];
Module. EXPORT_ OK = ['createnamespace ',
'Isdefined ',
'Modules ',
'Globalnamespace'];
Module. globalNamespace = this;
Module. modules = {'module': Module };
// Create namespace --> return a top namespace
Module. createNamespace = function (name, version ){
If (! Name) throw new Error ('name required ');
If (name. charAt (0) = '. '| name. charAt (name. length-1) = '. '| name. indexOf ('.. ')! =-1) throw new Error ('illegal name ');
Var parts = name. split ('.');
Var container = Module. globalNamespace;
For (var I = 0; I <parts. length; I ++ ){
Var part = parts [I];
If (! Container [part]) container [part] = {};
Container = container [part];
}
Var namespace = container;
If (namespace. NAME) throw new Error ('module "'+ name +'" is already defined ');
Namespace. NAME = name;
If (version) namespace. VERSION = version;
Module. modules [name] = namespace;
Return namespace;
};
// Check name is defined or not
Module. isDefined = function (name ){
Return name in Module. modules;
};
// Check version
Module. require = function (name, version ){
If (! (Name in Module. modules) throw new Error ('module '+ name +' is not defined ');
If (! Version) return;
Var n = Module. modules [name];
If (! N. VERSION | n. VERSION <version) throw new Error ('version' + version + 'or greater is required ');
};
// Import module
Module. importSymbols = function (from ){
If (typeof form = 'string') from = Module. modules [from];
Var to = Module. globalNamespace; // dafault
Var symbols = [];
Var firstsymbol = 1;
If (arguments. length> 1 & typeof arguments [1] = 'object' & arguments [1]! = Null ){
To = arguments [1];
Firstsymbol = 2;
}
For (var a = firstsymbol; a <arguments. length; a ++ ){
Symbols. push (arguments [a]);
}
If (symbols. length = 0 ){
// Default export list
If (from. EXPORT ){
For (var I = 0; I <from. EXPORT. length; I ++ ){
Var s = from. EXPORT [I];
To [s] = from [s];
}
Return;
} Else if (! From. EXPORT_ OK ){
// EXPORT array & EXPORT_ OK array both undefined
For (var s in from ){
To [s] = from [s];
Return;
}
}
}
If (symbols. length> 0 ){
Var allowed;
If (from. EXPORT | form. EXPORT_ OK ){
Allowed = {};
If (from. EXPORT ){
For (var I = 0; I <form. EXPORT. length; I ++ ){
Allowed [from. EXPORT [I] = true;
}
}
If (from. EXPORT_ OK ){
For (var I = 0; I <form. EXPORT_ OK .length; I ++ ){
Allowed [form. EXPORT_ OK [I] = true;
}
}
}
}
// Import the symbols
For (var I = 0; I <symbols. length; I ++ ){
Var s = symbols [I];
If (! (S in from) throw new Error ('symbol '+ s +' is not defined ');
If (!! Allowed &&! (S in allowed) throw new Error (s + 'is not public, cannot be imported ');
To [s] = form [s];
}
}