JavaScript 命名空间

15
我想为我的应用程序创建一个全局命名空间,并在该命名空间中创建其他命名空间:
例如:
Dashboard.Ajax.Post()

Dashboard.RetrieveContent.RefreshSalespersonPerformanceContent();

我还想将它们放在单独的文件中:

  • Ajax.js
  • RetrieveContent.js

然而,我尝试使用这种方法,但它不起作用,因为相同的变量名被用于两个不同的命名空间。是否有其他替代方法?

谢谢。


你想给你的命名空间取一个不同的名字吗? - meder omuraliev
我猜那是一个选项,但是我希望将所有内容都包含在一个命名空间下,因为我认为这样会更有序。 - Jamie C
11个回答

9

您只需要确保如果命名空间对象已经被创建,不要重复定义。可以像这样实现:

(function() {
    // private vars can go in here


    Dashboard = Dashboard || {};
    Dashboard.Ajax = {
        Post: function() {
            ...
        }
    };
})();

RetrieveContent文件的定义类似。


嗨,我也在尝试将我的代码放入命名空间中,但我有一个问题:我有很多JavaScript文件,那么我是否应该在每个文件中添加Dashboard = Dashboard || {};(如果这是我的命名空间)?其次,我是否必须在每个变量前加上Dashboard.x而不是x,还是因为所有内容都在同一命名空间中,所以这不是必要的? - Michel

7
这里有一篇关于JavaScript中各种“模块模式”的非常好的文章。其中有一个非常好的小节介绍了如何增强模块或命名空间,并维护跨文件的私有状态。也就是说,分别在不同的文件中编写的代码将按顺序执行,并在执行后正确地增强命名空间。
我没有深入研究过这个技术,所以不能保证...但基本思想是这样的。 dashboard.js
(function(window){

    var dashboard  = (function () { 
        var my = {}, 
            privateVariable = 1; 

        function privateMethod() { 
            // ... 
        } 

        my.moduleProperty = 1; 
        my.moduleMethod = function () { 
            // ... 
        }; 

        return my; 
    }());

    window.Dashboard = dashboard;
})(window);

dashboard.ajax.js

var dashboard = (function (my) { 
    var _private = my._private = my._private || {}, 
        _seal = my._seal = my._seal || function () { 
            delete my._private; 
            delete my._seal; 
            delete my._unseal; 
        }, 
        _unseal = my._unseal = my._unseal || function () { 
            my._private = _private; 
            my._seal = _seal; 
            my._unseal = _unseal; 
        }; 

    // permanent access to _private, _seal, and _unseal

    my.ajax = function(){ 
        // ...
    }

    return my; 
}(dashboard || {}));

dashboard.retrieveContent.js

var dashboard = (function (my) { 
    var _private = my._private = my._private || {}, 
        _seal = my._seal = my._seal || function () { 
            delete my._private; 
            delete my._seal; 
            delete my._unseal; 
        }, 
        _unseal = my._unseal = my._unseal || function () { 
            my._private = _private; 
            my._seal = _seal; 
            my._unseal = _unseal; 
        }; 

    // permanent access to _private, _seal, and _unseal

    my.retrieveContent = function(){ 
        // ...
    }

    return my; 
}(dashboard || {}));

你能否请解释一下,这是如何允许访问在另一个文件中声明的私有变量的?更具体地说,如果我调用dashboard._seal(),那么dashboard._unseal()又是如何让我再次访问这个私有变量的? - Parth Shah

4
Yahoo命名空间函数正是为解决这个问题而设计的。
补充: 该函数的源代码可供使用。如果需要,您可以将其复制到自己的代码中,并将根从YAHOO更改为其他内容等。

3
创建了NS对象之后,您应该可以从任何地方向其添加内容。虽然您可能想尝试var NS = NS || {};以确保NS对象存在且不被覆盖。
// NS is a global variable for a namespace for the app's code
var NS = NS || {};

NS.Obj = (function() {

  // Private vars and methods always available to returned object via closure
  var foo; // ...

  // Methods in here are public
  return {
    method: function() {

    }
  };

}());

3

如果您想使用或检查已经提供此类功能的预制(即经过测试的)解决方案,那么已经有几个库可以提供这样的功能。

最简单和最没有错误的方法是使用 jQuery.extend,并将 deep 参数设置为 true。我之所以说它没有错误,不是因为我认为 jQuery.extend 比其他库更少出错,而是因为它提供了一个明确的选项,可以从发送方深度复制属性到接收方,而大多数其他库则明确没有提供此功能。这将防止许多难以诊断的错误在程序后期出现,因为您使用了浅层复制的 extend,现在函数在您不希望它们执行的上下文中执行。(但是,如果您在设计方法时注意到将扩展基本库的方式,这应该不会成为问题。)


1

我强烈推荐您使用这种技术:

https://github.com/mckoss/namespace

  namespace.lookup('com.mydomain.mymodule').define(function (ns) {
  var external = namespace.lookup('com.domain.external-module');

  function myFunction() {
    ...
  }

  ...

  ns.extend({
    'myFunction': myFunction,
     ...
  });
});

我已经使用这种模式几年了;我希望更多的库也能做同样的事情;这让我在不同的项目之间共享代码变得更加容易。

1
你可以这样做...
使用命名空间库的HTML页面:
<html>
<head>
    <title>javascript namespacing</title>
    <script src="dashboard.js" type="text/javascript"></script>
    <script src="ajax.js" type="text/javascript"></script>
    <script src="retrieve_content.js" type="text/javascript"></script>
    <script type="text/javascript">
        alert(Dashboard.Ajax.Post());
        alert(Dashboard.RetrieveContent.RefreshSalespersonPerformanceContent());
        Dashboard.RetrieveContent.Settings.Timeout = 1500;
        alert(Dashboard.RetrieveContent.Settings.Timeout);
    </script>
</head>

<body>
    whatever...
</body>

</html>

Dashboard.js:

(function(window, undefined){
    var dashboard = {};
    window.Dashboard = dashboard;
})(window);

Ajax.js:

(function(){
    var ajax = {};
    ajax.Post = function() { return "Posted!" };
    window.Dashboard.Ajax = ajax
})();

Retrieve_Content.js:

(function(){
    var retrieveContent = {};
    retrieveContent.RefreshSalespersonPerformanceContent = function() { 
        return "content retrieved"
    };


    var _contentType;
    var _timeout;
    retrieveContent.Settings = {
        "ContentType": function(contentType) { _contentType = contentType; },
        "ContentType": function() { return _contentType; },
        "Timeout": function(timeout) { _timeout = timeout; },
        "Timeout": function() { return _timeout; }
    };

    window.Dashboard.RetrieveContent = retrieveContent;

})();

Dashboard.js 是其下所有命名空间的起点。其余部分在各自的文件中定义。在 Retrieve_Content.js 中,我添加了一些额外的属性在 Settings 下面,以便在需要时提供如何执行的想法。


虽然无法保证retrieve_content.js会在Dashboard.js之后被加载和解析。如果任何依赖库在Dashboard.js加载之前被加载,则分配将失败。 - Sean Vieira
1
通常情况下,Dashboard.js 会首先被加载和解析,但这并不是保证的。如果需要,可以在分配之前检查 Dashboard 对象并进行创建,但这将需要在 retrieve_content.jsajax.js 中重复一些代码。OP 的单独文件要求导致我做出了上述决定。 - ironsam

1
我编写了这个函数来简化命名空间的创建。也许它会对你有所帮助。
function ns(nsstr) {
    var t = nsstr.split('.');
    var obj = window[t[0]] = window[t[0]] || {};
    for (var i = 1; i < t.length; i++) {
        obj[t[i]] = obj[t[i]] || {};
        obj = obj[t[i]];
    }
}

ns('mynamespace.isawesome.andgreat.andstuff');
mynamespace.isawesome.andgreat.andstuff = 3;

console.log(mynamespace.isawesome.andgreat.andstuff);

1

仅提供链接的答案并不是很有用。这里应该有足够的解释。问题是如何从两个不同的位置定义命名空间中的对象。 - Ruan Mendes
1
@JuanMendes:这篇文章确切地阐述了这一点。此外,一些最优秀的答案基本上只是链接。而且,这已经超过一年半前的事了。 - Cristian Sanchez
我从未真正发现模块模式适合用于命名空间。 命名空间不是“可重用”的代码块(尽管命名空间的个别部分可能是)。 对象字面量对我始终运作得足够好。 - 1nfiniti

0

实现:

namespace = function(packageName)
{
    // Local variables.
    var layers, layer, currentLayer, i;

    // Split the given string into an array.
    // Each element represents a namespace layer.
    layers = packageName.split('.');

    // If the top layer does not exist in the global namespace.
    if (eval("typeof " + layers[0]) === 'undefined')
    {
        // Define the top layer in the global namesapce.
        eval(layers[0] + " = {};");
    }

    // Assign the top layer to 'currentLayer'.
    eval("currentLayer = " + layers[0] + ";");

    for (i = 1; i < layers.length; ++i)
    {
        // A layer name.
        layer = layers[i];

        // If the layer does not exist under the current layer.
        if (!(layer in currentLayer))
        {
            // Add the layer under the current layer.
            currentLayer[layer] = {};
        }

        // Down to the next layer.
        currentLayer = currentLayer[layer];
    }

    // Return the hash object that represents the last layer.
    return currentLayer;
};


结果:

namespace('Dashboard.Ajax').Post = function() {
    ......
};

namespace('Dashboard.RetrieveContent').RefreshSalespersonPerformanceContent = function() {
    ......
};


概要:

namespace.js


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接