在JavaScript中如何声明命名空间?

1074

如何创建JavaScript中的命名空间,以便我的对象和函数不会被其他同名的对象和函数覆盖?我已经使用了以下代码:

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

有没有更优雅或更简洁的方法来做这件事?


21
我能理解你的想法,即检查命名空间是否被占用,但由于如果此操作失败,对象将不会被创建,因此我认为更好的方法是在命名空间已被占用时发出警告。坦白地说,在大多数 JavaScript 情况下,这种情况应该不会发生,并且应该很快在开发过程中被捕获。 - annakata
19
拥有一个顶层“命名空间”(窗口属性)。早期测试应该能够检测到冲突。不要费心添加所有这些“如果”的检查。重复的“命名空间”是致命问题,应该像这样处理。你可以像jQuery一样采取一种方法来允许使用自定义“命名空间”,但这仍然是设计时的问题。 - user166390
请参见 https://dev59.com/PkvSa4cB1Zd3GeqPeXKv 以了解 JavaScript 命名空间技术的性能问题。 - Tim Abell
请参见https://dev59.com/Am855IYBdhLWcg3w0H93,了解有关JavaScript中使用对象和函数命名空间的区别。 - Tim Abell
这是大量的信息,但真正阐述了不同JS设计模式之间的差异。它对我帮助很大:http://addyosmani.com/resources/essentialjsdesignpatterns/book/ - Justin
1
现在我们有符号和模块,所以重复的命名空间甚至不应该成为一个问题。 - Sebastian Simon
29个回答

1073

我使用企业级jQuery网站上找到的方法

这里是他们的示例,展示如何声明私有和公共属性和函数。所有操作都是通过自执行匿名函数完成的。

(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;

    //Public Property
    skillet.ingredient = "Bacon Strips";

    //Public Method
    skillet.fry = function() {
        var oliveOil;

        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };

    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }
}( window.skillet = window.skillet || {}, jQuery ));

所以,如果你想要访问其中一个公共成员,只需使用skillet.fry()skillet.ingredients即可。
真正酷的是,你现在可以使用完全相同的语法扩展命名空间。
//Adding new Functionality to the skillet
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";

    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " +
                     skillet.ingredient + " & " +
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
    };
}( window.skillet = window.skillet || {}, jQuery ));

第三个 undefined 参数

第三个 undefined 参数是变量值 undefined 的来源。我不确定它今天是否仍然相关,但在使用旧版本浏览器 / JavaScript标准(ECMAScript 5,JavaScript <1.8.5〜Firefox 4),全局作用域变量 undefined 是可写的,因此任何人都可以重写其值。当未传递值时,第三个参数(undefined)会创建一个名为 undefined 的变量,该变量作用域限定于命名空间/函数中。因为在创建名称空间时没有传递值,所以它默认为值 undefined


10
对这个很棒的示例点赞。对于任何有兴趣的人,这个示例是Elijah Manor在Mix 2011上精彩演讲的一部分(忽略标题)http://live.visitmix.com/MIX11/Sessions/Speaker/Elijah-Manor - Darren Lewis
13
从伊莱贾的文章中,以下是这种方法的优缺点的释义。优点:1.公共和私有属性和方法,2.不使用繁琐的OLN,3.保护未定义的4.确保$指的是jQuery,5.命名空间可以跨文件。缺点:比OLN更难理解。 - Jared Beck
6
这被称为今天的 IIFE(即时调用函数表达式)。感谢您的回答 +1! - Gustavo Gondim
23
@CpILL说:不确定是否仍然相关,但第三个undefined参数是值为undefined的变量源。在使用旧版浏览器/JavaScript标准(ECMAScript 5,JavaScript<1.8.5〜Firefox 4)时,全局范围的变量undefined是可写的,因此任何人都可以重写其值。添加第三个额外的参数,您没有传递它的值为undefined,因此您正在创建命名空间范围的undefined,它不会被外部源重写。 - mrówa
8
window.skillet = window.skillet || {} 的好处在于,它允许多个脚本安全地添加到同一个命名空间中,即使它们不提前知道执行顺序。这样做的好处是,无论您想随意重新排序脚本引用而不会破坏代码,还是想使用 async 属性 异步加载脚本并不确定执行顺序,都可以帮助您。详情请参见 https://dev59.com/4Gw15IYBdhLWcg3wuuCn。 - Mark Amery
显示剩余11条评论

814

我喜欢这个:

var yourNamespace = {

    foo: function() {
    },

    bar: function() {
    }
};

...

yourNamespace.foo();

68
重要的一点是要严格遵循只扩展一个根变量的规则,所有的事情都必须从这个出发。 - annakata
24
这并不会为你的代码创建一个封闭空间,而是让调用其他函数变得繁琐,因为它们总是需要像这样看起来:yourNamespace.bar();我创建了一个开源项目,专门解决这个设计问题:http://github.com/mckoss/namespace。 - mckoss
28
为什么“重要的是要坚定不移地扩展到不超过一个根变量”的说法很重要?这是因为限制只扩展一个根变量可以帮助保持代码的简洁性和可读性,并减少错误的可能性。当我们尝试同时更改多个变量时,很容易导致代码混乱和难以维护。因此,专注于一个变量可以提高代码的可靠性和可维护性。 - user406905
12
为什么应该有一个浅对象结构? - Ryan
28
@Ryan 我的意思是一切都应该在“MyApp”下面,例如MyApp.Views.Profile = {}而不是MyApp.users = {}MyViews.Profile = {}。并不一定意味着只能有两个级别的深度。 - alex
显示剩余6条评论

357

另一种方法是这样的,我认为它比对象字面量形式要稍微灵活一些:

var ns = new function() {

    var internalFunction = function() {

    };

    this.publicFunction = function() {

    };
};

上面的内容很像模块模式不管你喜欢不喜欢, 它允许您将所有函数公开为公共函数,同时避免了对象字面量的严格结构。

18
  1. OLN 和模块模式是有区别的。
  2. 我并不总是喜欢 OLN,因为你需要记住不要在最后一个逗号后加上空值,还有你必须初始化所有的属性(如 null 或 undefined)。此外,如果你需要闭包来实现成员函数,你需要为每个方法创建小型函数工厂。另外一点是你必须将所有控制结构都包含在函数内部,而上述形式则没有这种限制。
这并不是说我不使用 OLN,只是有时候我不太喜欢它。
- Ionuț G. Stan
11
我喜欢这种方法是因为它允许使用私有函数、变量和伪常量(例如:var API_KEY = 12345;)。 - Lawrence Barsanti
12
我喜欢这个比得票更高的逗号分隔对象容器更多。我也没有看到任何比较上的缺点。我错过了什么吗? - Lucent
7
JS新手问题:为什么我不需要输入 ns().publicFunction(),而是可以直接使用 ns.publicFunction() - John Kraft
15
@John Kraft,这是因为在function关键字前面有new关键字。基本上,它正在声明一个匿名函数(作为函数,它也是一个构造函数),然后立即使用new将其作为构造函数调用。因此,存储在ns中的最终值是该匿名构造函数的(唯一)实例。希望这样说得通。 - Ionuț G. Stan
显示剩余7条评论

160

有更优雅或简洁的方法吗?

有。例如:

var your_namespace = your_namespace || {};

然后你就可以拥有了

var your_namespace = your_namespace || {};
your_namespace.Foo = {toAlert:'test'};
your_namespace.Bar = function(arg) 
{
    alert(arg);
};
with(your_namespace)
{
   Bar(Foo.toAlert);
}

1
这段代码在IE7中会出现错误。使用以下代码 var your_namespace = (typeof your_namespace == "undefined" || !your_namespace ) ? {} : your_namespace ; 可以更好地解决问题。 - mjallday
9
以下是翻译的结果: 应该这样写:var your_namespace = your_namespace = your_namespace || {},适用于所有浏览器 ;) - Palo
2
@Palo,您能否解释一下为什么应该这样写?var your_namespace = your_namespace = your_namespace || {} - Sriram
6
您可以在不同的JS文件中扩展your_namespace对象。如果使用var your_namespace = {},则无法这样做,因为每个文件都会覆盖该对象。 - Alex Pacurar
1
然而,MDN不鼓励使用with吗? - aamarks
显示剩余3条评论

97

我通常在一个闭包中构建它:

var MYNS = MYNS || {};

MYNS.subns = (function() {

    function privateMethod() {
        // Do private stuff, or build internal.
        return "Message";
    }

    return {
        someProperty: 'prop value',
        publicMethod: function() {
            return privateMethod() + " stuff";
        }
    };
})();

自从我写下这句话以来,我的风格已经有了微妙的变化,现在我发现自己会像这样编写闭包:

var MYNS = MYNS || {};

MYNS.subns = (function() {
    var internalState = "Message";

    var privateMethod = function() {
        // Do private stuff, or build internal.
        return internalState;
    };
    var publicMethod = function() {
        return privateMethod() + " stuff";
    };

    return {
        someProperty: 'prop value',
        publicMethod: publicMethod
    };
})();

以这种方式,我发现公共API和实现更易于理解。将返回语句视为对实现的公共接口。


4
你是否应该检查MYNS.subns = MYNS.subns || {}的值? - Paranoid Android
一个好的点,应该是开发人员意图的练习。你需要考虑当它存在时该怎么做,替换它、报错、使用现有的或版本检查并有条件地替换。我遇到了不同的情况,需要使用每个变体。在大多数情况下,你可能会把这个作为一个低风险的边缘案例,并且替换可能是有益的,考虑一个试图劫持NS的流氓模块。 - Brett Ryan
3
如果有人手上有《Speaking JavaScript》这本书,在第412页的“Quick and Dirty Modules”一节中,有关于这种方法的解释。 - Soferio
3
优化提示:虽然var foo = functionfunction foo是类似的,因为它们都是私有的;但由于JavaScript的动态类型特性,后者稍微更快,因为它在大多数解释器的流水线中跳过了一些指令。对于var foo,必须调用类型系统才能找到要分配给该变量的类型,而对于function foo,类型系统自动知道它是一个函数,因此跳过了几个函数调用,这意味着CPU指令的少数调用,例如jmppushqpopq等,这会使CPU流水线变短。 - Braden Best
2
@brett 哎呀,你说得对。我想到了另一种脚本语言。虽然我仍然坚持 function foo 语法更易读。而且我仍然喜欢我的版本。 - Braden Best
显示剩余3条评论

59

因为您可能会编写不同的JavaScript文件,然后将它们合并或不合并到应用程序中,所以每个文件需要能够恢复或构建命名空间对象,而不会损坏其他文件的工作...

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

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

namespace.namespace1.doSomeThing = function(){}

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

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

namespace.namespace2.doSomeThing = function(){}

这两个文件可以共存或分开而不会冲突。


1
我发现这是一种非常有用的方法,可以在需要将功能模块化的大型应用程序中将客户端脚本组织成多个文件。 - DVK
有关程序设计的内容,将以下文本从英语翻译成中文。仅返回已翻译的文本:询问多个文件的特定问题:https://dev59.com/E2435IYBdhLWcg3w50f4 - Ciro Santilli OurBigBook.com

54
这是Stoyan Stefanov在他的JavaScript Patterns书中的做法,我发现非常好(它还展示了如何添加注释以便自动生成API文档,以及如何向自定义对象的原型中添加方法):
/**
* My JavaScript application
*
* @module myapp
*/

/** @namespace Namespace for MYAPP classes and functions. */
var MYAPP = MYAPP || {};

/**
* A maths utility
* @namespace MYAPP
* @class math_stuff
*/
MYAPP.math_stuff = {

    /**
    * Sums two numbers
    *
    * @method sum
    * @param {Number} a First number
    * @param {Number} b Second number
    * @return {Number} Sum of the inputs
    */
    sum: function (a, b) {
        return a + b;
    },

    /**
    * Multiplies two numbers
    *
    * @method multi
    * @param {Number} a First number
    * @param {Number} b Second number
    * @return {Number} The inputs multiplied
    */
    multi: function (a, b) {
        return a * b;
    }
};

/**
* Constructs Person objects
* @class Person
* @constructor
* @namespace MYAPP
* @param {String} First name
* @param {String} Last name
*/
MYAPP.Person = function (first, last) {

    /**
    * First name of the Person
    * @property first_name
    * @type String
    */
    this.first_name = first;

    /**
    * Last name of the Person
    * @property last_name
    * @type String
    */
    this.last_name = last;
};

/**
* Return Person's full name
*
* @method getName
* @return {String} First name + last name
*/
MYAPP.Person.prototype.getName = function () {
    return this.first_name + ' ' + this.last_name;
};

33

我使用以下方法:

var myNamespace = {}
myNamespace._construct = function()
{
    var staticVariable = "This is available to all functions created here"

    function MyClass()
    {
       // Depending on the class, we may build all the classes here
       this.publicMethod = function()
       {
          //Do stuff
       }
    }

    // Alternatively, we may use a prototype.
    MyClass.prototype.altPublicMethod = function()
    {
        //Do stuff
    }

    function privateStuff()
    {
    }

    function publicStuff()
    {
       // Code that may call other public and private functions
    }

    // List of things to place publically
    this.publicStuff = publicStuff
    this.MyClass = MyClass
}
myNamespace._construct()

// The following may or may not be in another file
myNamespace.subName = {}
myNamespace.subName._construct = function()
{
   // Build namespace
}
myNamespace.subName._construct()

然后可以使用外部代码:

var myClass = new myNamespace.MyClass();
var myOtherClass = new myNamepace.subName.SomeOtherClass();
myNamespace.subName.publicOtherStuff(someParameter);

@yossiba:可能。上面的代码是相当标准的东西。在标准的JS中,任何函数都可以用作构造函数,你不需要做任何标记来指定一个函数专门用作构造函数。你是在使用像ActionScript这样的非常规语言吗? - AnthonyWJones
@Anthony,最好使用var MYNAMESPACE = MYNAMESPACE || {}; 只使用var myNamespace = {}是不安全的,而且最好将命名空间声明为大写。 - paul
10
“更好”的概念很主观。我不喜欢阅读大写字母的代码,所以我避免使用所有字母都大写的标识符。尽管 ns = ns || {} 看起来更加严谨,但它可能会导致其他意想不到的结果。 - AnthonyWJones
@Anthony 这只是一个建议,并遵循惯例。此外,这完全取决于您的个人喜好。 - paul
@AnthonyWJones,除了错误地使用意外的变量之外,您预计ns = ns || {}会产生什么意外的结果?更为谨慎的方法可能是var n = n && n.name === "NSNAME" ? n : { name: "NSNAME"}; - Brett Ryan
显示剩余2条评论

32

这是对user106826提供的Namespace.js链接的跟进。该项目似乎已经迁移到GitHub,现在位于smith/namespacedotjs

我一直在为我的小型项目使用这个简单的JavaScript帮助程序,到目前为止,它似乎轻巧而且足够灵活,可以处理命名空间和加载模块/类。如果它可以让我将包导入到我选择的命名空间中,而不仅仅是全局命名空间,那就太好了……唉,但这已经不是重点了。

它允许您声明命名空间,然后在该命名空间中定义对象/模块:

Namespace('my.awesome.package');
my.awesome.package.WildClass = {};

另一个选择是一次性声明命名空间及其内容:

Namespace('my.awesome.package', {
    SuperDuperClass: {
        saveTheDay: function() {
            alert('You are welcome.');
        }
    }
});

如果需要更多的用法示例,请查看源代码中的example.js文件。


2
只要你记住这会对性能产生一些影响,因为每次访问my.awesome.package.WildClass时,你都在访问my的awesome属性,my.awesome的package属性和my.awesome.package的WildClass属性。 - SamStephens

29

示例:

var namespace = {};
namespace.module1 = (function(){

    var self = {};
    self.initialized = false;

    self.init = function(){
        setTimeout(self.onTimeout, 1000)
    };

    self.onTimeout = function(){
        alert('onTimeout')
        self.initialized = true;
    };

    self.init(); /* If it needs to auto-initialize, */
    /* You can also call 'namespace.module1.init();' from outside the module. */
    return self;
})()

如果想将一个变量声明为私有变量,可以选择性地声明一个local变量same,类似于self并将local.onTimeout赋值给它。


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