【问题标题】:Javascript module Pattern with Namespacing带有命名空间的 Javascript 模块模式
【发布时间】:2025-12-02 15:05:01
【问题描述】:

我一直在练习具有适当名称间距的 JavaScript 模块模式。所以基本上我声明了命名空间,每个命名空间都封装了某些模块。这是我到目前为止所写的。代码已正确注释。

// namespace has been defined somewhere not to worry that it will be undefined
if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        var _all_ids = {};
        var persona = {};
        var _id = id; // _id is a local variable that stores the argument

        var getId = function () { //this function returns the local private variable
            return _id;
        }

        persona.getId = getId;
        var _closed = false;

        var close = function () {
            delete _all_ids[getId()];
            this._closed = true;
        }

        persona.close = close;

        return persona; // persona is an object that has two properties `getId` and `close`. Both these are functiona
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
} 

我已经对代码进行了完整的注释,任何人都可以理解。它声明一个简单的命名空间,然后在其中添加一个模块。该模块当然是使用封装。

我的一位导师建议此代码具有基本的缺陷标准或其他。尽管代码运行良好,但我无法弄清楚这一点。

标准是否足够好?还是我做错了?

【问题讨论】:

  • 这是完整的模块吗?通常一个模块被封装在一个生命周期中
  • @daniel_L 是的,这是完整的模块——并且还声明了立即函数,但我没有将它包含在此处的代码中。它只是一个关于我如何尝试创建命名空间的示例。
  • 您的设置NAMESPACE['id'] = id。这将使您的命名空间具有var id 后面的功能,而不是模块接口,因为您还没有运行该功能。最好返回作为模块接口的角色对象,并让 NAMESPACE 决定模块的去向。这样您就可以重用模块,而无需依赖于您的命名空间将所有模块都置于其*范围内。
  • @shilly 很有趣!但是如果你注意到我最后返回了persona objectid 返回角色对象,所以无论如何我基本上都会返回它。
  • @Talha,仅当您转到 NAMESPACE['id'] = id(); 时,您将 namespace.id 设置为函数 ID,而不是函数返回的对象。

标签: javascript namespaces module-pattern code-standards


【解决方案1】:

我稍微扩展了示例以显示原理。

// NAMESPACE with install/define method, like when using require.js
var NAMESPACE = (function ( doc, win ) {
    var _modules = {};
    return {
        'install' : function ( name, definition ) {
            if (!_modules.hasOwnProperty(name)) {
                _modules[name] = definition;
                this[name] = _modules[name];
            }
            else throw new Error('Module ' + name + ' is already installed.');
        }
    };
}( document, window ));
// Your actual module to use in the NAMESPACE
NAMESPACE.install('id', (function ( id ) {
    var _all_ids = {}, 
        _id = id, 
        _closed = false; 
    return { 
        'getID' : function () { 
            return _id; 
        }, 
        'close' : function () { 
            delete _all_ids[_id];
            _closed = true; 
        } 
    }; 
}( 'someDefaultID' )));

// NAMESPACE as an object we pass around
var NAMESPACE = {};
(function ( NAMESPACE ) {
    var id,
        NAMESPACE = NAMESPACE;
    if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
        NAMESPACE = {};
        window.NAMESPACE = NAMESPACE;
    }
    id = (function ( id ) {
        var _all_ids = {}, 
            _id = id, 
            _closed = false; 
        return { 
            'getID' : function () { 
                return _id; 
            }, 
            'close' : function () { 
                delete _all_ids[_id];
                _closed = true; 
            } 
        }; 
    }( 'someDefaultID' ));
    NAMESPACE['id'] = id;
}( window.NAMESPACE ))

【讨论】:

  • 我同意这是一种改进。来自我的 +1
【解决方案2】:

为什么不把函数放在命名空间中?

NAMESPACE = {
 id: function (id) {
  // The id function in here
 }
}

【讨论】:

  • @Lekoaf 是的,我认为这不是我们要追求的重点。请再次阅读问题。
【解决方案3】:

命名空间的重点是避免污染全局范围。在您的代码中,

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        // ...
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
}

id 现在在全局范围以及您的命名空间中定义。这违背了使用命名空间的目的。

实现定义命名空间而不污染全局范围的相同目标的简单纯 JS 方法是重新排列

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    NAMESPACE['id'] = function (id) {
        // ...
    }
}

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {
        id: function (id) {
        // ...
    }

}

正如@Lekoaf 建议的那样。

但是,避免全球污染的现代方法是将所有代码简单地包含在 IIFE 中。虽然有不止一种方法可以将您的代码包含在 IIFE 中,但最简单的方法是按原样将其放入

(function(){ // Beginning of IIFE
if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        var _all_ids = {};
        var persona = {};
        var _id = id; // _id is a local variable that stores the argument

        var getId = function () { //this function returns the local private variable
            return _id;
        }

        persona.getId = getId;
        var _closed = false;

        var close = function () {
            delete _all_ids[getId()];
            this._closed = true;
        }

        persona.close = close;

        return persona; // persona is an object that has two properties `getId` and `close`. Both these are functiona
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
} 
})(); // End of IIFE

【讨论】:

  • 我会部分同意你,尤其是你最后的代码 sn-p。是的,我们可以在一个模块中定义全局变量,也可以在同一个模块中使用它们。私有方法和变量也可以通过显示 API 来显示。
【解决方案4】:

您可以使用扩充模块模式来避免覆盖您的全局模块及其方法。

//fooModule does not exist, so it is defined as an empty object
var fooModule = (function(app, name, definition) {
    if (!app[name]) app[name] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "foobar", function() {
    console.log("foobar");
});

//fooModule now exists, so we use the pre-existing object instead of an empty one
var fooModule = (function(app, name, defintion) {
    if (!app[name]) app[namepace] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "barfoo", function() {
    console.log("barfoo");
});

//throws an error because barfoo has already been set to fooModule
var fooModule = (function(app, name, defintion) {
    if (!app[name]) app[namepace] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "barfoo", function() {
    console.log("should throw error");
});

fooModule.foobar(); //"foobar"
fooModule.barfoo(); //"barfoo"    

重要的部分是myModule || {}。如果模块存在,则使用它,如果不存在,则创建模块。

这仅涉及全局对象上的方法。你读过this article吗?可能是我读过的关于模块模式的最好的。

【讨论】: