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

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

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

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

620967 次浏览

我喜欢这个:

var yourNamespace = {
foo: function() {},
bar: function() {}};
...
yourNamespace.foo();

我使用这种方法:

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 herethis.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 publicallythis.publicStuff = publicStuffthis.MyClass = MyClass}myNamespace._construct()
// The following may or may not be in another filemyNamespace.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);

另一种方法,我认为它比对象字面形式的限制少一点,是这样的:

var ns = new function() {
var internalFunction = function() {
};
this.publicFunction = function() {
};};

上面的非常类似于的模块模式不管你喜欢与否,它允许您将所有函数公开为public,同时避免对象文字的刚性结构。

在将我的几个库移植到不同的项目之后,并且必须不断更改顶级(静态命名)命名空间,我已经切换到使用这个小型(开源)辅助函数来定义命名空间。

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

好处的描述在我的博客文章。你可以抓住源代码在这里

我真正喜欢的好处之一是模块之间在加载顺序方面的隔离。您可以在加载外部模块之前引用它。当代码可用时,您获得的对象引用将被填充。

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

可以。例如:

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);}

我创建了命名空间,它受到Erlang模块的启发。这是一种非常实用的方法,但这就是我现在编写JavaScript代码的方式。

它为闭包提供一个全局命名空间,并在该闭包中公开一个定义的集合函数。

(function(){
namespace("images", previous, next);// ^^ This creates or finds a root object, images, and binds the two functions to it.// It works even though those functions are not yet defined.
function previous(){ ... }
function next(){ ... }
function find(){ ... } // A private function
})();

这是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文件。

因为您可能会编写不同的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(){}

这两个文件可以一起存在或分开而不会发生冲突。

我编写了另一个命名空间库,它的工作方式更像其他语言中的包/单元。它允许您创建一个JavaScript代码包以及从其他代码中打包的引用:

文件hello.js

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

文件Example.js

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

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

您可以在包JS中找到相关项目。

如果使用Makefile,您可以做到这一点。

// prelude.hjsbilly = new (function moduleWrapper () {const exports = this;
// postlude.hjsreturn exports;})();
// someinternalfile.jsfunction bob () { console.log('hi'); }exports.bob = bob;
// clientfile.jsbilly.bob();

无论如何,一旦我到达大约1000行,我更喜欢使用Makefile,因为我可以通过删除makefile中的一行来有效地注释掉大量代码。它很容易摆弄东西。此外,使用这种技术,命名空间在前奏中只出现一次,所以很容易更改,你不必在库代码中不断重复它。

使用makefile时在浏览器中进行实时开发的外壳脚本:

while (true); do make; sleep 1; done

将其添加为make任务'go',您可以'make go'来保持您的构建在编码时更新。

我使用在Enterprise jQuery站点上找到的方法

这是他们的示例,展示了如何声明私有和公共属性和函数。一切都是作为一个自动执行的匿名函数完成的。

(function( skillet, $, undefined ) {//Private Propertyvar isHot = true;
//Public Propertyskillet.ingredient = "Bacon Strips";
//Public Methodskillet.fry = function() {var oliveOil;
addItem( "\t\n Butter \n\t" );addItem( oliveOil );console.log( "Frying " + skillet.ingredient );};
//Private Methodfunction 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 Propertyvar amountOfGrease = "1 Cup";
//Public Methodskillet.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

我通常在闭包中构建它:

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和实现更容易理解。将返回语句视为实现的公共接口。

这是Stoyan Stefan ov在他的JavaScript模式书中的做法,我发现这本书非常好(它还展示了他如何进行允许自动生成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;};

您可以声明一个简单的函数来提供命名空间。

function namespace(namespace) {var object = this, tokens = namespace.split("."), token;
while (tokens.length > 0) {token = tokens.shift();
if (typeof object[token] === "undefined") {object[token] = {};}
object = object[token];}
return object;}
// Usage examplenamespace("foo.bar").baz = "I'm a value!";

样本:

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

我喜欢Jaco PRETORIUS的解决方案,但我想通过将“this”关键字指向模块/命名空间对象来使其更有用。我的煎锅版本:

(function ($, undefined) {
console.log(this);
}).call(window.myNamespace = window.myNamespace || {}, jQuery);

我对命名空间使用以下语法。

var MYNamespace = MYNamespace|| {};
MYNamespace.MyFirstClass = function (val) {this.value = val;this.getValue = function(){return this.value;};}
var myFirstInstance = new MYNamespace.MyFirstClass(46);alert(myFirstInstance.getValue());

代码说明jsfiddle:http://jsfiddle.net/rpaul/4dngxwb3/1/

我的习惯是使用函数myName()作为属性存储,然后使用var myName作为“方法”持有者。

不管这是否足够合法,打败我!我一直依赖我的PHP逻辑,事情很简单。: D

function myObj() {this.prop1 = 1;this.prop2 = 2;this.prop3 = 'string';}
var myObj = ((myObj instanceof Function !== false)? Object.create({
$props: new myObj(),fName1: function() { /* code..  */ },fName2: function() { /* code ...*/ }}): console.log('Object creation failed!'));

if (this !== that) myObj.fName1(); else myObj.fName2();

您也可以以“反之亦然”的方式在创建对象之前进行检查,即好多了

function myObj() {this.prop1 = 1;this.prop2 = 2;this.prop3 = 'string';}
var myObj = ((typeof(myObj) !== "function" || myObj instanceof Function === false)? new Boolean(): Object.create({$props: new myObj(),init: function () { return; },fName1: function() { /* code..  */ },fName2: function() { /* code ...*/ }}));
if (myObj instanceof Boolean) {Object.freeze(myObj);console.log('myObj failed!');debugger;}elsemyObj.init();

参考:JavaScript:使用Object.create()创建对象

这是Ionu G. Stan的回答的后续,但通过使用var ClassFirst = this.ClassFirst = function() {...}展示了整洁代码的好处,它利用了JavaScript的闭包范围,减少了同一命名空间中类的命名空间混乱。

var Namespace = new function() {var ClassFirst = this.ClassFirst = function() {this.abc = 123;}
var ClassSecond = this.ClassSecond = function() {console.log("Cluttered way to access another class in namespace: ", new Namespace.ClassFirst().abc);console.log("Nicer way to access a class in same namespace: ", new ClassFirst().abc);}}
var Namespace2 = new function() {var ClassFirst = this.ClassFirst = function() {this.abc = 666;}
var ClassSecond = this.ClassSecond = function() {console.log("Cluttered way to access another class in namespace: ", new Namespace2.ClassFirst().abc);console.log("Nicer way to access a class in same namespace: ", new ClassFirst().abc);}}
new Namespace.ClassSecond()new Namespace2.ClassSecond()

输出:

Cluttered way to access another class in namespace: 123Nicer way to access a class in same namespace: 123Cluttered way to access another class in namespace: 666Nicer way to access a class in same namespace: 666

我们可以这样独立地使用它:

var A = A|| {};A.B = {};
A.B = {itemOne: null,itemTwo: null,};
A.B.itemOne = function () {//..}
A.B.itemTwo = function () {//..}

我最喜欢的模式最近变成了这样:

var namespace = (function() {  
// expose to publicreturn {a: internalA,c: internalC}
// all private  
/*** Full JSDoc*/function internalA() {// ...}  
/*** Full JSDoc*/function internalB() {// ...}  
/*** Full JSDoc*/function internalC() {// ...}  
/*** Full JSDoc*/function internalD() {// ...}  
})();

当然,返回可以放在最后,但如果只有函数声明跟随它,那么更容易看到命名空间的全部内容以及暴露的API。

在这种情况下使用函数表达式的模式导致无法在不查看整个代码的情况下知道暴露了哪些方法。

在JavaScript中,没有预定义的方法来使用命名空间。在JavaScript中,我们必须创建自己的方法来定义命名空间。这是我们在Oodles技术中遵循的过程。

注册一个命名空间以下是注册名称空间的函数

//Register NameSpaces Functionfunction registerNS(args){var nameSpaceParts = args.split(".");var root = window;
for(var i=0; i < nameSpaceParts.length; i++){if(typeof root[nameSpaceParts[i]] == "undefined")root[nameSpaceParts[i]] = new Object();
root = root[nameSpaceParts[i]];}}

要注册命名空间,只需调用上面的函数,参数为名称空间,由'.'(点)分隔。举个例子让您的应用程序名称大量。您可以通过以下方法创建命名空间

registerNS("oodles.HomeUtilities");registerNS("oodles.GlobalUtilities");var $OHU = oodles.HomeUtilities;var $OGU = oodles.GlobalUtilities;

基本上,它将在后端创建您的NameSpace结构,如下所示:

var oodles = {"HomeUtilities": {},"GlobalUtilities": {}};

在上面的函数中,您注册了一个名为"oodles.HomeUtilities""oodles.GlobalUtilities"的命名空间。为了调用这些命名空间,我们创建了一个变量,即var$OHU和var$OGU

这些变量只不过是实例化命名空间的别名。现在,每当你声明一个属于HomeUtilities的函数时,你会像下面这样声明它:

$OHU.initialization = function(){//Your Code Here};

上面是函数名初始化,它被放入一个命名空间$OHU。并在脚本文件的任何位置调用此函数。只需使用以下代码。

$OHU.initialization();

同样,与另一个名称空间。

希望有帮助。

我迟到了7年,但在8年前做了相当多的工作:

重要的是能够轻松有效地创建多个嵌套命名空间,以保持复杂的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;};

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

(function(){const namespace = function(name, separator, container){var o = container || window;name.split(separator || '.').forEach(function(x){o = o[x] = o[x] || {};});return o;};const ns = namespace("com.ziesemer.myApp");	
// Optional:ns.namespace = ns;	
// Further extend, work with ns from here...}());
console.log("\"com\":", com);

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

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

在使用模块模式时,我们可能会发现定义一个用于开始使用它的简单模板很有用。这是一个涵盖名称行间距、公共和私有变量的模板。

在JavaScript中,模块模式用于进一步模拟类的概念,使我们能够在单个对象中包含公共/私有方法和变量,从而将特定部分与全局范围屏蔽开来。这导致我们的函数名称与页面上其他脚本中定义的其他函数冲突的可能性降低。

var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variablemyPrivateVar = 0;
// A private function which logs any argumentsmyPrivateMethod = function( foo ) {console.log( foo );};
return {
// A public variablemyPublicVar: "foo",
// A public function utilizing privatesmyPublicFunction: function( bar ) {
// Increment our private countermyPrivateVar++;
// Call our private method using barmyPrivateMethod( bar );
}};
})();

优势

为什么模块模式是一个不错的选择?对于初学者来说,它比真正的封装概念更清晰,至少从JavaScript的角度来看。

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

缺点

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

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

揭示模块模式

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

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

他的努力的结果是一个更新的模式,我们只需在私有范围内定义我们所有的函数和变量,并返回一个匿名对象,其中包含指向我们希望公开的私有功能的指针。

如何使用揭示模块模式的示例可以在下面找到

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" );

优势

这种模式使我们脚本的语法更加一致。它还使模块末尾的哪些函数和变量可以公开访问更加清晰,从而简化了易读性。

缺点

这种模式的一个缺点是,如果私有函数引用公共函数,如果需要补丁,则无法覆盖该公共函数。这是因为私有函数将继续引用私有实现,并且该模式不适用于公共成员,只适用于函数。

引用私有变量的公共对象成员也受制于上面的无补丁规则注释。

如果您需要私有范围:

var yourNamespace = (function() {
//Private propertyvar publicScope = {};
//Private propertyvar privateProperty = "aaa";
//Public propertypublicScope.publicProperty = "bbb";
//Public methodpublicScope.publicMethod = function() {this.privateMethod();};
//Private methodfunction privateMethod() {console.log(this.privateProperty);}
//Return only the public partsreturn publicScope;}());
yourNamespace.publicMethod();

否则,如果您永远不会使用私有范围:

var yourNamespace = {};
yourNamespace.publicMethod = function() {// Do something...};
yourNamespace.publicMethod2 = function() {// Do something...};
yourNamespace.publicMethod();

我认为你们都为这样一个简单的问题使用了太多的代码。没有必要为此做一个回购。这是一个单行函数。

namespace => namespace.split(".").reduce((last, next) => (last[next] = (last[next] || {})), window);

试试看:

// --- definition ---const namespace = name => name.split(".").reduce((last, next) => (last[next] = (last[next] || {})), window);
// --- Use ----const c = namespace("a.b.c");c.MyClass = class MyClass {};
// --- see ----console.log("a : ", a);

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

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

ES6模块命名空间导入

// circle.jsexport { name, draw, reportArea, reportPerimeter };
// main.jsimport * as Circle from './modules/circle.js';
// draw a circlelet circle1 = Circle.draw(myCanvas.ctx, 75, 200, 100, 'green');Circle.reportArea(circle1.radius, reportList);Circle.reportPerimeter(circle1.radius, reportList);

这会获取circle.js中可用的所有导出,并使它们作为对象Circle的成员可用,从而有效地为其提供自己的命名空间。

JavaScript还没有命名空间的原生表示,但TypeScript有。

例如,您可以使用以下TS代码(游乐场

namespace Stack {export const hello = () => console.log('hi')}
Stack.hello()

如果您无法将代码更新为TS,您至少可以在为命名空间生成JS输出时使用TS使用的模式,如下所示:

var Stack;(function (Stack) {Stack.hello = () => console.log('hi');})(Stack || (Stack = {}));Stack.hello();

进一步阅读: