`
robinqu
  • 浏览: 90409 次
  • 性别: 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( ) {};
}

分享到:
评论

相关推荐

    javascript高级编程JavaScript.pdf

    为了解决这个问题,开发者引入了“命名空间”的概念,尽管JavaScript本身并不直接支持命名空间,但可以通过对象模拟实现。 命名空间的主要目的是为了组织代码,避免全局作用域中的名称冲突。在JavaScript中,我们...

    浅析JavaScript中命名空间namespace模式

    在JavaScript中,命名空间(namespace)是一种组织代码的方式,用于解决由于全局变量过多或第三方库引入导致的命名冲突问题。由于JavaScript没有内置的命名空间或包管理机制,开发者通常通过模拟实现这一概念。在...

    基于JavaScript 下namespace 功能的简单分析

    标题中提到的“基于JavaScript下namespace功能的简单分析”,指的是在JavaScript中实现命名空间的概念和方法。命名空间在编程中用来组织代码,避免命名冲突和更方便地管理代码。当项目规模增大,或者需要将代码分割...

    javascript 类和命名空间的模拟代码

    下面将详细解释如何使用JavaScript模拟类和命名空间。 首先,我们来看看如何模拟命名空间。命名空间的主要目的是避免全局变量污染,提高代码的可读性和可维护性。在JavaScript中,可以使用对象字面量或函数来模拟...

    jquery命名空间模拟

    本篇文章将深入探讨如何在JavaScript中模拟jQuery的命名空间,以实现更有序、更安全的代码组织。 首先,我们需要理解JavaScript的命名空间是如何工作的。由于JavaScript本身没有内置的命名空间机制,我们通常通过...

    javascript 面向对象,实现namespace,class,继承,重载

    在JavaScript中,虽然原生不支持这些特性,但可以通过模拟来实现。 1. **命名空间(Namespace)**: 命名空间主要用于避免全局变量污染和命名冲突。在JavaScript中,我们可以利用对象作为容器来模拟命名空间。如...

    javascript DOM 操作.doc

    载入 XML 数据:在FireFox中,只支持load方法,可以通过parseFromString方法解析字符串型的xml数据,模拟loadXML方法。 ```javascript oXmlDom.load("example.xml"); // 载入xml文件 oXmlDom.parseFromString(...

    JavaScript编码标准1

    页面专属的JavaScript文件则存于`script/{module_name}`,模拟的JSON数据位于`script/json`,按页面分文件夹,而MVC框架中的模板文件则在`script/templates`,同样按页面分文件夹。 2. **代码格式化**: - 遵循...

    Javascript 命名空间模式

    在JavaScript这种没有原生命名空间支持的语言中,通过创建一个全局对象并将所有功能附加到这个对象下面,可以模拟实现类似命名空间的效果。这种方式通常用于大型项目或库,以保持代码的整洁和可维护性。 在描述中...

    JavaScript 编程引入命名空间的方法与代码

    Javascript 本身没有命名空间的概念,需要用对象模拟出来。 比如定义一个命名空间的类,用于创建命名空间: function NameSpace(){ } 这是一个构造函数,但却不做任何事情,再来下面和评论有关的代码: var ...

    js-fb-autopoke:用JavaScript编写的Facebook自动戳脚本

    用JavaScript编写的Facebook自动搜索脚本。 您必须在网页上才能启动此脚本。 如何使用它? 偶尔使用 在Firefox上,谷歌浏览器: 右键单击网页 选择检查元素 选择控制台 粘贴代码: src/autopoke.js 按回车 经常...

    JavaScript 命名空间 使用介绍

    但在JavaScript中,通常使用函数作用域来模拟命名空间的行为。 由于JavaScript的函数作用域特性,所有的变量和函数都属于这个作用域,而JavaScript中的块级作用域(例如使用if语句或循环创建的作用域)并不会创建一...

    JavaScript 面向对象之命名空间

    在JavaScript中,对象是基于原型的,类的概念是模拟出来的,通常通过构造函数和原型链来实现。而命名空间的实现并不涉及类的概念,它更像是一个组织代码的工具,帮助我们更好地管理和维护JavaScript代码。 总的来说...

    JavaScript 编程引入命名空间的方法

    Javascript 本身没有命名空间的概念,需要用对象模拟出来。 比如定义一个命名空间的类,用于创建命名空间: function NameSpace(){ } 这是一个构造函数,但却不做任何事情,再来下面和评论有关的代码: var ...

    Javascript 面向对象 命名空间

    在JavaScript中虽然没有像Java或者.NET那样的命名空间声明,但通过约定俗成的方式来模拟命名空间,以避免全局作用域中变量名的冲突。下面将详细介绍如何在JavaScript中创建和使用命名空间。 首先,命名空间在...

    深入理解JavaScript系列(47):对象创建模式(上篇)

    由于JavaScript没有内置的私有成员机制,我们可以通过构造函数内部的变量和函数来模拟私有性: ```javascript function Gadget() { var name = 'iPod'; // 私有对象 this.getName = function () { // 公有函数 ...

    objects-to-organize-code:JavaScript模式

    1. **命名空间(Namespace)模拟**:在JavaScript中,由于全局变量可能导致命名冲突,使用对象可以模拟命名空间,将一组相关的变量和函数封装在一起。例如,我们可以创建一个名为`MyApp`的对象,其中包含所有与应用...

Global site tag (gtag.js) - Google Analytics