如何创建JavaScript中的命名空间,以便我的对象和函数不会被其他同名的对象和函数覆盖?我已经使用了以下代码:
if (Foo == null || typeof(Foo) != "object") { var Foo = new Object();}
有没有更优雅或更简洁的方法来做这件事?
如何创建JavaScript中的命名空间,以便我的对象和函数不会被其他同名的对象和函数覆盖?我已经使用了以下代码:
if (Foo == null || typeof(Foo) != "object") { var Foo = new Object();}
有没有更优雅或更简洁的方法来做这件事?
模块模式最初是作为一种在传统软件工程中为类提供私有和公共封装的方式而定义的。
在使用模块模式时,我们可能会发现定义一个简单的模板来开始使用它很有用。这个模板可以涵盖命名空间、公共和私有变量。
在 JavaScript 中,模块模式用于进一步模拟类的概念,以这样的方式包括单个对象内的公共/私有方法和变量,从而使特定部分不受全局范围的影响。这导致函数名与页面上其他脚本中定义的函数发生冲突的可能性减少。
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的角度来看,相对于真正的封装思想,它更适合有面向对象背景的开发者。其次,它支持私有数据 - 所以,在模块模式中,我们的代码的公共部分可以访问私有部分,但外部世界无法访问类的私有部分。
缺点
模块模式的缺点在于,由于我们以不同的方式访问公共和私有成员,当我们希望改变可见性时,我们实际上必须更改使用该成员的每个地方。
我们还不能访问后期添加到对象中的方法的私有成员。尽管如此,在许多情况下,模块模式仍然非常有用,并且在正确使用时,肯定有潜力改善应用程序的结构。
揭示模块模式
现在我们对模块模式稍微熟悉一些了,让我们来看一下稍微改进的版本 - 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 property
var publicScope = {};
//Private property
var privateProperty = "aaa";
//Public property
publicScope.publicProperty = "bbb";
//Public method
publicScope.publicMethod = function() {
this.privateMethod();
};
//Private method
function privateMethod() {
console.log(this.privateProperty);
}
//Return only the public parts
return publicScope;
}());
yourNamespace.publicMethod();
否则,如果您永远不会使用私有作用域:
var yourNamespace = {};
yourNamespace.publicMethod = function() {
// Do something...
};
yourNamespace.publicMethod2 = function() {
// Do something...
};
yourNamespace.publicMethod();
你可以声明一个简单的函数来提供命名空间。
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 example
namespace("foo.bar").baz = "I'm a value!";
虽然我晚了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应用程序)。 如果保留名称空间函数,则其他文件可以重复使用它(在上面作为“可选项”包含)。 最坏的情况是,如果该函数被重复声明几次 - 它只是几行代码,如果被压缩则更少。
let
和 const
的问题。一旦你创建了一个命名空间 namespace("com.ziesemer.myApp")
,是否可以将属性/对象指定为 const
或 let
?比如 com.ziesemer.myApp.logger
- 在我看来,这似乎永远不能声明为 const
。我认为这样做就无法采用一些较新的语言特性。注意:不幸的是,采用新的Javascript模块不是一个选项。 - onefootswill(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
})();
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/
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);
// circle.js
export { name, draw, reportArea, reportPerimeter };
// main.js
import * as Circle from './modules/circle.js';
// draw a circle
let circle1 = Circle.draw(myCanvas.ctx, 75, 200, 100, 'green');
Circle.reportArea(circle1.radius, reportList);
Circle.reportPerimeter(circle1.radius, reportList);
这会获取 circle.js 内可用的所有导出内容,并将它们作为 Circle
对象的成员进行使用,从而有效地给它赋予了自己的命名空间。
我最近最喜欢的模式是这个:
var namespace = (function() {
// expose to public
return {
a: internalA,
c: internalC
}
// all private
/**
* Full JSDoc
*/
function internalA() {
// ...
}
/**
* Full JSDoc
*/
function internalB() {
// ...
}
/**
* Full JSDoc
*/
function internalC() {
// ...
}
/**
* Full JSDoc
*/
function internalD() {
// ...
}
})();
namespace.a();
。 - olimart()
。它们是必需的,很容易被忽略。我遇到了与 @olimart 相同的问题,并通过添加它们来解决了这个问题。 - Andres