`
robinqu
  • 浏览: 88677 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

JavaScript Namespace模拟

阅读更多
做JavaScript的大型项目比较痛苦,它没有namespace的概念,所有顶级变量都在global对象里面混杂起来。特别是现在比较流行模块式开发,问题就越来越糟糕了,做出来的东西可能会把别人现有工程给搞残废……哈哈

看看老外是怎么解决这个问题的……

引用
If you want to write a module of JavaScript code that can be used by any script and can be used with any other module, the most important rule you must follow is to avoid defining global variables.


第一要义是尽量避免定义全局变量。

// Create an empty object as our namespace
// This single global symbol will hold all of our other symbols
var Class = {};
// Define functions within the namespace
Class.define = function(data) { /* code goes here */ }
Class.provides = function(o, c) { /* code goes here */ }


引用
Note that you're not defining instance methods (or even static methods) of a JavaScript class here. You are defining ordinary functions and storing references to them within a specially created object instead of in the global object.
a module should never add more than a single symbol to the global namespace.

If a module adds a symbol to the global namespace, its documentation should clearly state what that symbol is.

If a module adds a symbol to the global namespace, there should be a clear relationship between the name of that symbol and the name of the file from which the module is loaded.


上面这个例子干脆新建一个Class对象来包含所有函数。这里从意义上就不是定义Class对象的实例方法,而是“在Class这个namespace下定义两个函数”。我们可以说Class是一个“模块”。
我们要求模块不能添加除自己以外的全局变量。这个要尽量做到。

如果的确添加了其他的全局变量,也要在文档中说明关系。

如果有两个同名而内容不同的模块,你只有学学JAVA的包了,采用com.yoursite.base之类的命名,并放在拥有同样结构的文件夹下面。

在编写模块的时候,我们可能会这样:
// Create the global symbol "com" if it doesn't exist
// Throw an error if it does exist but is not an object
var com;
if (!com) com = {};
else if (typeof com != "object")
    throw new Error("com already exists and is not an object");

// Repeat the creation and type-checking code for the next level
if (!com.davidflanagan) com.davidflanagan = {}
else if (typeof com.davidflanagan != "object")
    throw new Error("com.davidflanagan already exists and is not an object");

// Throw an error if com.davidflanagan.Class already exists
if (com.davidflanagan.Class)
    throw new Error("com.davidflanagan.Class already exists");

// Otherwise, create and populate the namespace with one big object literal
com.davidflanagan.Class = {
    define: function(data) { /* code here */ },
    provides: function(o, c) { /* code here */ }
};

一步一步判断,你定义的“包”是否存已经存在,然后在来写详细内容。

Testing the Availability of a Module
测试某个模块是否可用

var com;  // Declare global symbol before testing for its presence
if (!com || !com.davidflanagan || !com.davidflanagan.Class)
    throw new Error("com/davidflanagan/Class.js has not been loaded");


有的模块是自己初始化的,有的需要在某个特定的时间调用初始化过程

在网页中,不应该包含任何JS代码,这意味我们必须要为我们的模块指定相关的初始化机制。

Importing Symbols from Namespaces
在具体使用的时候,我们总不能用com.yoursite.base.Class这么长的名字。往往,我们更希望能将埋得很深的变量引入到顶层,方便使用。
// This is an easier name, to save typing.
var define = com.davidflanagan.Class.define;


引用
It is the module developer's responsibility to use namespaces to prevent collisions. But it is the module user's prerogative to import symbols from the module's namespace into the global namespace. The programmer using the module will know what other modules are in use and what all the potential name collisions are. She can determine what symbols to import and how to import them to prevent collisions.

But changing method names is not fully satisfactory, either. Another programmer who has used the module before might find the name defineClass( ) confusing because he is familiar with the function under the name define( ).
Also, module developers often put quite a bit of thought into their function names, and changing these names may not do justice to the module.


你可能要郁闷了,好不容易把你的模块中的变量埋得好好的,你又挖出来干啥?
使用namespace是模块编写人员的职责,而程序员可以掌握全局变量的状况,所以暴露到全局变量中也无妨。

在引入命名空间中变量的过程中,我们最好遵循编写者的一定命名规则。上例中,最好这样命名:

// Create a simple namespace. No error checking required.  The
// module user knows that this symbol does not exist yet.
var Class = {};
// Now import a symbol into this new namespace.
Class.define = com.davidflanagan.Class.define;


引用

you can import only symbols that refer to a function, object, or array. If you import a symbol whose value is a primitive type such as a number or a string, you simply get a static copy of the value. Any changes to the value occur in the namespace and are not reflected in an imported copy of the value.


在引入过程中,如果你引入的是对象、数组、函数,那么是可读写的。但是如果你引入的是原始数据类型,例如数字、字符串,你仅仅得到了一份拷贝,对其更改并不会在本体中有什么影响。例如下例,只是得到了counter的一个副本:
// Make a static copy only. Changes in the namespace are not
// reflected in the imported property since this is a primitive value.
Class.counter = com.davidflanagan.Class.counter;

引用

The lesson for module developers is if your module defines properties that refer to primitive values, you should provide accessor methods that can be imported


解决方法当然是提供属性的连接器getter或setter了。

// A property of primitive type; cannot be imported
com.davidflanagan.Class.counter = 0;

// Here is an accessor method that can be imported
com.davidflanagan.Class.getCounter = function( ) {
    return com.davidflanagan.Class.counter;
}

引用

Module developers must always use the fully qualified name of their symbols.

Functions that call other functions in the module must use their fully qualified name so that they work correctly even when invoked without having been imported. (An exception to this rule, involving closures )


在开发模块的时候,应该总是使用变量的完整路径名,以免产生错误。除非你采用了闭包。

Public and Private Symbols
公共和私有变量

引用
Not all the symbols defined in a module's namespace are intended for external use.
The most straightforward approach is simple documentation.

A convention that can help to make the public/private distinction clear, even without reference to the documentation is to prefix private symbols with an underscore.


并不是模块中所有的变量或函数都是给别人用的,你可能希望能对外界隐藏它们。遗憾的是,JS不能直接支持这种特性。

我们可以通过在文档中著名那些是公共方法,那些是私有方法。或者在私有方法或属性前面加上一个下划线,例如:Class._counter

当然,我们还可以闭包在真正意义上让某个变量“私有”。如下面的例子:

// Create the namespace object.  Error checking omitted here for brevity.
var com;
if (!com) com = {};
if (!com.davidflanagan) com.davidflanagan = {};
com.davidflanagan.Class = {};

// Don't stick anything into the namespace directly.
// Instead we define and invoke an anonymous function to create a closure
// that serves as our private namespace. This function will export its
// public symbols from the closure into the com.davidflanagan.Class object
// Note that we use an unnamed function so we don't create any other
// global symbols.
(function( ) {  // Begin anonymous function definition
    // Nested functions create symbols within the closure
    function define(data) { counter++; /* more code here */ }
    function provides(o, c) { /* code here */ }

    // Local variable are symbols within the closure.
    // This one will remain private within the closure
    var counter = 0;

    // This function can refer to the variable with a simple name
    // instead of having to qualify it with a namespace
    function getCounter( ) { return counter; }

    // Now that we've defined the properties we want in our private
    // closure, we can export the public ones to the public namespace
    // and leave the private ones hidden here.
    var ns = com.davidflanagan.Class;
    ns.define = define;
    ns.provides = provides;
    ns.getCounter = getCounter;
})( );          // End anonymous function definition and invoke it


最后给出一个非常NB的模块辅助类Module,可以帮你创建namespace,导入、判定等等操作。

先说说用法:
创建命名空间并定义模块
// Create a namespace for our module
Module.createNamespace("com.davidflanagan.Class");

// Now start populating the namespace
com.davidflanagan.Class.define = function(data) { /* code here */ };
com.davidflanagan.Class.provides = function(o, c) { /* code here */ };


判定这个版本的类是否加载,如果没有就会抛出异常
// This Complex module requires the Class module to be loaded first
Module.require("com.davidflanagan.Class", 1.0);


Module.importSymbols()可以将某个类引入到指定的namespace,如果第二个参数为空,则引入到全局对象
// Import the default set of Module symbols to the global namespace
// One of these defualt symbols is importSymbols itself
Module.importSymbols(Module); // Note we pass the namespace, not module name

// Import the Complex class into the global namespace
importSymbols(com.davidflanagan.Complex);

// Import the com.davidflanagan.Class.define( ) method to a Class object
var Class = {};
importSymbols(com.davidflanagan.Class, Class, "define");


Module.registerInitializationFunction( ) 则是注册模块的初始化函数;
Module.runInitializationFunctions( )则是手动运行初始化函数

Module类的实现如下:
/**
 * Module.js: module and namspace utilities
 *
 * This is a module of module-related utility functions that are
 * compatible with JSAN-type modules.
 * This module defines the namespace Module.
 */

// Make sure we haven't already been loaded
var Module;
if (Module && (typeof Module != "object" || Module.NAME))
    throw new Error("Namespace 'Module' already exists");

// Create our namespace
Module = {};

// This is some metainformation about this namespace
Module.NAME = "Module";    // The name of this namespace
Module.VERSION = 0.1;      // The version of this namespace

// This is the list of public symbols that we export from this namespace.
// These are of interest to programers who use modules.
Module.EXPORT = ["require", "importSymbols"];

// These are other symbols we are willing to export. They are ones normally
// used only by module authors and are not typically imported.
Module.EXPORT_OK = ["createNamespace", "isDefined",
                    "registerInitializationFunction",
                    "runInitializationFunctions",
                    "modules", "globalNamespace"];


// Now start adding symbols to the namespace
Module.globalNamespace = this;  // So we can always refer to the global scope
Module.modules = { "Module": Module };  // Module name->namespace map.

/**
 * This function creates and returns a namespace object for the
 * specified name and does useful error checking to ensure that the
 * name does not conflict with any previously loaded module. It
 * throws an error if the namespace already exists or if any of the
 * property components of the namespace exist and are not objects.
 *
 * Sets a NAME property of the new namespace to its name.
 * If the version argument is specified, set the VERSION property
 * of the namespace.
 *
 * A mapping for the new namespace is added to the Module.modules object
 */
Module.createNamespace = function(name, version) {
    // Check name for validity.  It must exist, and must not begin or
    // end with a period or contain two periods in a row.
    if (!name) throw new Error("Module.createNamespace( ): name required");
    if (name.charAt(0) == '.' ||
        name.charAt(name.length-1) == '.' ||
        name.indexOf("..") != -1)
        throw new Error("Module.createNamespace( ): illegal name: " + name);

    // Break the name at periods and create the object hierarchy we need
    var parts = name.split('.');

    // For each namespace component, either create an object or ensure that
    // an object by that name already exists.
    var container = Module.globalNamespace;
    for(var i = 0; i < parts.length; i++) {
        var part = parts[i];
        // If there is no property of container with this name, create
        // an empty object.
        if (!container[part]) container[part] = {};
        else if (typeof container[part] != "object") {
            // If there is already a property, make sure it is an object
            var n = parts.slice(0,i).join('.');
            throw new Error(n + " already exists and is not an object");
        }
        container = container[part];
    }

    // The last container traversed above is the namespace we need.
    var namespace = container;

    // It is an error to define a namespace twice. It is okay if our
    // namespace object already exists, but it must not already have a
    // NAME property defined.
    if (namespace.NAME) throw new Error("Module "+name+" is already defined");

    // Initialize name and version fields of the namespace
    namespace.NAME = name;
    if (version) namespace.VERSION = version;

    // Register this namespace in the map of all modules
    Module.modules[name] = namespace;

    // Return the namespace object to the caller
    return namespace;
}
/**
 * Test whether the module with the specified name has been defined.
 * Returns true if it is defined and false otherwise.
 */
Module.isDefined = function(name) {
    return name in Module.modules;
};

/**
 * This function throws an error if the named module is not defined
 * or if it is defined but its version is less than the specified version.
 * If the namespace exists and has a suitable version, this function simply
 * returns without doing anything. Use this function to cause a fatal
 * error if the modules that your code requires are not present.
 */
Module.require = function(name, version) {
    if (!(name in Module.modules)) {
        throw new Error("Module " + name + " is not defined");
    }

    // If no version was specified, there is nothing to check
    if (!version) return;

    var n = Module.modules[name];

    // If the defined version is less than the required version or if
    // the namespace does not declare any version, throw an error.
    if (!n.VERSION || n.VERSION < version)
    throw new Error("Module " + name + " has version " +
                    n.VERSION + " but version " + version +
                    " or greater is required.");
};

/**
 * This function imports symbols from a specified module.  By default, it
 * imports them into the global namespace, but you may specify a different
 * destination as the second argument.
 *
 * If no symbols are explicitly specified, the symbols in the EXPORT
 * array of the module will be imported. If no such array is defined,
 * and no EXPORT_OK is defined, all symbols from the module will be imported.
 *
 * To import an explicitly specified set of symbols, pass their names as
 * arguments after the module and the optional destination namespace. If the
 * modules defines an EXPORT or EXPORT_OK array, symbols will be imported
 * only if they are listed in one of those arrays.
 */
Module.importSymbols = function(from) {
    // Make sure that the module is correctly specified. We expect the
    // module's namespace object but will try with a string, too
    if (typeof from == "string") from = Module.modules[from];
    if (!from || typeof from != "object")
        throw new Error("Module.importSymbols( ): " +
                        "namespace object required");

    // The source namespace may be followed by an optional destination
    // namespace and the names of one or more symbols to import;
    var to = Module.globalNamespace; // Default destination
    var symbols = [];                // No symbols by default
    var firstsymbol = 1;             // Index in arguments of first symbol name

    // See if a destination namespace is specified
    if (arguments.length > 1 && typeof arguments[1] == "object") {
        if (arguments[1] != null) to = arguments[1];
        firstsymbol = 2;
    }

    // Now get the list of specified symbols
    for(var a = firstsymbol; a < arguments.length; a++)
        symbols.push(arguments[a]);

    // If we were not passed any symbols to import, import a set defined
    // by the module, or just import all of them.
    if (symbols.length == 0) {
        // If the module defines an EXPORT array, import
        // the symbols in that array.
        if (from.EXPORT) {
            for(var i = 0; i < from.EXPORT.length; i++) {
                var s = from.EXPORT[i];
                to[s] = from[s];
            }
            return;
        }
        // Otherwise if the modules does not define an EXPORT_OK array,
        // just import everything in the module's namespace
        else if (!from.EXPORT_OK) {
            for(s in from) to[s] = from[s];
            return;
        }
    }

    // If we get here, we have an explicitly specified array of symbols
    // to import. If the namespace defines EXPORT and/or EXPORT_OK arrays,
    // ensure that each symbol is listed before importing it.
    // Throw an error if a requested symbol does not exist or if
    // it is not allowed to be exported.
    var allowed;
    if (from.EXPORT || from.EXPORT_OK) {
        allowed = {};
        // Copy allowed symbols from arrays to properties of an object.
        // This allows us to test for an allowed symbol more efficiently.
        if (from.EXPORT)
            for(var i = 0; i < from.EXPORT.length; i++)
                allowed[from.EXPORT[i]] = true;
        if (from.EXPORT_OK)
            for(var i = 0; i < from.EXPORT_OK.length; i++)
                allowed[from.EXPORT_OK[i]] = true;
    }

    // Import the symbols
    for(var i = 0; i < symbols.length; i++) {
        var s = symbols[i];              // The name of the symbol to import
        if (!(s in from))                // Make sure it exists
            throw new Error("Module.importSymbols( ): symbol " + s +
                            " is not defined");
        if (allowed && !(s in allowed))  // Make sure it is a public symbol
            throw new Error("Module.importSymbols( ): symbol " + s +
                            " is not public and cannot be imported.");
        to[s] = from[s];                 // Import it
    }
};


// Modules use this function to register one or more initialization functions
Module.registerInitializationFunction = function(f) {
    // Store the function in the array of initialization functions
    Module._initfuncs.push(f);
    // If we have not yet registered an onload event handler, do so now.
    Module._registerEventHandler( );
}
// A function to invoke all registered initialization functions.
// In client-side JavaScript, this will automatically be called in
// when the document finished loading. In other contexts, you must
// call it explicitly.
Module.runInitializationFunctions = function( ) {
    // Run each initialization function, catching and ignoring exceptions
    // so that a failure by one module does not prevent other modules
    // from being initialized.
    for(var i = 0; i < Module._initfuncs.length; i++) {
        try { Module._initfuncs[i]( ); }
        catch(e) { /* ignore exceptions */}
    }
    // Erase the array so the functions are never called more than once.
    Module._initfuncs.length = 0;
}

// A private array holding initialization functions to invoke later
Module._initfuncs = [];

// If we are loaded into a web browser, this private function registers an
// onload event handler to run the initialization functions for all loaded
// modules. It does not allow itself to be called more than once.
Module._registerEventHandler = function( ) {
    var clientside =   // Check for well-known client-side properties
        "window" in Module.globalNamespace &&
        "navigator" in window;

    if (clientside) {
        if (window.addEventListener) {  // W3C DOM standard event registration
            window.addEventListener("load", Module.runInitializationFunctions,
                                    false);
        }
        else if (window.attachEvent) {  // IE5+ event registration
            window.attachEvent("onload", Module.runInitializationFunctions);
        }
        else {
            // IE4 and old browsers
            // If the <body> defines an onload tag, this event listener
            // will be overwritten and never get called.
            window.onload = Module.runInitializationFunctions;
        }
    }

    // The function overwrites itself with an empty function so it never
    // gets called more than once.
    Module._registerEventHandler = function( ) {};
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics