如何在JavaScript中创建命名空间,使我的对象和函数不被其他同名对象和函数覆盖?我使用了以下方法:

if (Foo == null || typeof(Foo) != "object") { var Foo = new Object();}

有没有更优雅或更简洁的方式来做到这一点?


当前回答

JavaScript默认情况下不支持命名空间。因此,如果您创建任何元素(函数、方法、对象、变量),那么它将变成全局的,并污染全局命名空间。让我们以定义两个没有任何名称空间的函数为例,

function func1() {
    console.log("This is a first definition");

}
function func1() {
    console.log("This is a second definition");
}
func1(); // This is a second definition

它总是调用第二个函数定义。在这种情况下,命名空间将解决名称冲突问题。

其他回答

因为您可能会编写不同的JavaScript文件,然后在应用程序中组合或不组合它们,所以每个文件都需要能够恢复或构造名称空间对象,而不会破坏其他文件的工作……

一个文件可能打算使用命名空间namespace.namespace1:

namespace = window.namespace || {};
namespace.namespace1 = namespace.namespace1 || {};

namespace.namespace1.doSomeThing = function(){}

另一个文件可能想要使用命名空间namespace2:

namespace = window.namespace || {};
namespace.namespace2 = namespace.namespace2 || {};

namespace.namespace2.doSomeThing = function(){}

这两个文件可以共存,也可以分开而不会发生碰撞。

我写了另一个命名空间库,它的工作方式有点像其他语言中的包/单元。它允许你创建一个JavaScript代码包,并从其他代码中引用这个包:

文件hello.js

Package("hello", [], function() {
  function greeting() {
    alert("Hello World!");
  }
  // Expose function greeting to other packages
  Export("greeting", greeting);
});

文件Example.js

Package("example", ["hello"], function(greeting) {
  // Greeting is available here
  greeting();  // Alerts: "Hello World!"
});

页面中只需要包含第二个文件。它的依赖项(本例中的文件hello.js)将自动加载,从这些依赖项导出的对象将用于填充回调函数的参数。

你可以在Packages JS中找到相关的项目。

模块模式最初被定义为在传统软件工程中为类提供私有和公共封装的一种方式。

在使用Module模式时,我们可能会发现定义一个简单的模板来开始使用它是很有用的。下面是一个涵盖了名称间距、公共变量和私有变量的例子。

在JavaScript中,Module模式被用来进一步模拟类的概念,这样我们就可以在单个对象中同时包含公共/私有方法和变量,从而将特定部分从全局作用域中屏蔽掉。这样做的结果是降低了我们的函数名与页面上其他脚本中定义的其他函数冲突的可能性。

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();

优势

为什么模块模式是一个很好的选择?首先,对于来自面向对象背景的开发人员来说,它比真正的封装思想要简洁得多,至少从JavaScript的角度来看是这样。

其次,它支持私有数据——因此,在模块模式中,代码的公共部分能够触及私有部分,而外部世界无法触及类的私有部分。

缺点

Module模式的缺点是,由于我们以不同的方式访问公共成员和私有成员,当我们希望更改可见性时,实际上必须对使用成员的每个位置进行更改。

我们也不能访问稍后添加到对象的方法中的私有成员。也就是说,在许多情况下,模块模式仍然是非常有用的,如果使用正确,肯定有潜力改善我们的应用程序的结构。

揭示模块模式

现在我们对模块模式有了一些熟悉,让我们来看看稍微改进的版本——Christian Heilmann的揭示模块模式。

当我们想要从一个公共方法调用另一个公共方法或访问公共变量时,他不得不重复主对象的名称,这使Heilmann感到沮丧,因此出现了揭示模块模式。他也不喜欢模块模式的要求,因为他希望公开的东西必须切换到对象文字符号。

他努力的结果是一种更新的模式,在这种模式中,我们只需在私有作用域中定义所有函数和变量,并返回一个匿名对象,该对象带有指向我们希望显示为公共的私有功能的指针。

下面是如何使用reveal Module模式的示例

var myRevealingModule = (function () {

        var privateVar = "Ben Cherry",
            publicVar = "Hey there!";

        function privateFunction() {
            console.log( "Name:" + privateVar );
        }

        function publicSetName( strName ) {
            privateVar = strName;
        }

        function publicGetName() {
            privateFunction();
        }


        // Reveal public pointers to
        // private functions and properties

        return {
            setName: publicSetName,
            greeting: publicVar,
            getName: publicGetName
        };

    })();

myRevealingModule.setName( "Paul Kinlan" );

优势

这种模式使得脚本的语法更加一致。在模块的末尾,它还使哪些函数和变量可以被公开访问更加清楚,从而降低了可读性。

缺点

这种模式的一个缺点是,如果一个私有函数引用了一个公共函数,那么如果需要一个补丁,这个公共函数就不能被覆盖。这是因为私有函数将继续引用私有实现,并且该模式不应用于公共成员,只应用于函数。

引用私有变量的公共对象成员也遵循上面的无补丁规则。

虽然我已经晚了7年,但是在8年前我已经为此做了很多工作:

http://blogger.ziesemer.com/2008/05/javascript-namespace-function.html http://blogger.ziesemer.com/2007/10/respecting-javascript-global-namespace.html

能够轻松有效地创建多个嵌套的名称空间,以保持复杂的web应用程序的组织和管理,同时尊重JavaScript全局名称空间(防止名称空间污染),并且在这样做的时候不破坏名称空间路径中的任何现有对象,这是很重要的。

以上是我在2008年前后的解决方案:

var namespace = function(name, separator, container){
  var ns = name.split(separator || '.'),
    o = container || window,
    i,
    len;
  for(i = 0, len = ns.length; i < len; i++){
    o = o[ns[i]] = o[ns[i]] || {};
  }
  return o;
};

这不是创建名称空间,而是提供了创建名称空间的函数。

这可以浓缩成一个简化的一行代码:

var namespace=function(c,f,b){var e=c.split(f||"."),g=b||window,d,a;for(d=0,a=e.length;d<a;d++){g=g[e[d]]=g[e[d]]||{}}return g};

使用示例:

namespace("com.example.namespace");
com.example.namespace.test = function(){
  alert("In namespaced function.");
};

或者,作为一种说法:

namespace("com.example.namespace").test = function(){
  alert("In namespaced function.");
};

然后执行如下:

com.example.namespace.test();

如果你不需要支持旧浏览器,更新版本:

const namespace = function(name, separator, container){
    var o = container || window;
    name.split(separator || '.').forEach(function(x){
        o = o[x] = o[x] || {};
    });
    return o;
};

现在,我对将命名空间暴露给全局命名空间本身持谨慎态度。(可惜基础语言没有为我们提供这个!)所以我通常会在闭包中使用这个,比如:

(函数(){ Const namespace = function(名称,分隔符,容器){ Var o =容器||窗口; 的名字。split(separator || '.'). foreach(函数(x){ O = O [x] = O [x] || {}; }); 返回啊; }; const ns = namespace("com.ziesemer.myApp"); / /可选: ns.namespace = ns; //进一步扩展,使用ns从这里… } ()); console.log (\ com \”:“com);

在较大的应用程序中,这只需要在页面加载开始时定义一次(对于基于客户端的web应用程序)。如果保留的话,其他文件可以重用命名空间函数(在上面包含为“可选”)。在最坏的情况下,如果这个函数被重新声明了几次——它只需要几行代码,如果简化了,就会更少。

在将我的几个库移植到不同的项目中,并且不得不不断更改顶级(静态命名的)名称空间之后,我转而使用这个小的(开源的)帮助函数来定义名称空间。

global_namespace.Define('startpad.base', function(ns) {
    var Other = ns.Import('startpad.other');
    ....
});

关于好处的描述在我的博客文章中。您可以在这里获取源代码。

我非常喜欢的一个好处是模块之间相对于加载顺序的隔离。你可以在加载外部模块之前引用它。当代码可用时,将填充您获得的对象引用。