JavaScript 命名空间——寻找一种简单有效的方法

Posted

技术标签:

【中文标题】JavaScript 命名空间——寻找一种简单有效的方法【英文标题】:JavaScript namespacing - looking for a simple and efficient approach 【发布时间】:2013-03-23 05:59:05 【问题描述】:

我想在一个项目中实现 javascript 命名空间。我和我的同事都不是 JavaScript 专家,因此我们正在寻找一种简单有效的命名空间方法。可读性是一项巨大的资产,我们是 Java 开发人员,很容易成为 JavaScript 陷阱的猎物。

我们编写了以下概念证明:

<html><body><script>
var mynamespace = ;
mynamespace.variable = "inside mynamespace";
mynamespace.level1 = ;
mynamespace.level1.level2 = ;

(function()
    this.variable = "inside mynamespace.level1";
    this.print = function() return "print() : " + this.variable ;
).apply(mynamespace.level1);

(function()
    this.variable = "inside mynamespace.level1.level2";
    this.print = function() return "print() : " + this.variable ;
).apply(mynamespace.level1.level2);

// We can use "that" instead of "this"; however I do not see any benefit in doing it...
(function()
    if(!mynamespace.level1.level2b) this.level2b = ; var that = this.level2b ;
    that.variable = "inside mynamespace.level1.level2b";
    that.print = function() return "print() : " + this.variable ;
).apply(mynamespace.level1);

document.write(mynamespace.level1.print() + "<br>"); // print() : inside mynamespace.level1
document.write(mynamespace.level1.variable + "<br>");// inside mynamespace.level1

document.write(mynamespace.level1.level2.print() + "<br>");// print() : inside mynamespace.level1.level2
document.write(mynamespace.level1.level2.variable + "<br>");// inside mynamespace.level1.level2

document.write(mynamespace.level1.level2b.print() + "<br>");// print() : inside mynamespace.level1.level2b
document.write(mynamespace.level1.level2b.variable + "<br>");// inside mynamespace.level1.level2b

document.write(mynamespace.variable + "<br>");// inside mynamespace
document.write(mynamespace.something + "<br>");// undefined
</script></body></html>

=== 问题===

第一季度。您认为这是一个好方法吗?

第二季度。我们会遇到什么陷阱吗?

第三季度。您有更好的建议吗(请考虑简单性、可读性)?

非常感谢任何帮助。

干杯,

编辑: 我们将使用 jQuery。所以,我们会考虑使用 jQuery 的更好方法。

【问题讨论】:

阅读 requirejs 而不是命名所有内容 您的问题相当广泛。改用 Code Review 可能会找到更好的回复。 addyosmani.com/blog/essential-js-namespacing 这是一个很好的资源。请进一步阅读 Javascript 中的更多设计模式。 既然你来自 Java - 来自这篇文章“require.js 可以帮助将 javascript 代码组织成模块,这些模块的行为方式类似于 java 等编程语言中的包”...imediava.wordpress.com/2012/04/23/intro-require-js @CiananSims,本质上,我只想知道“是的,看起来不错”或“不这样做,你会遇到问题”。我不知道代码审查,将在那里看看。感谢您的建议。 【参考方案1】:

即使是简单的项目,我也总是使用 JavaScript 命名空间。我正在使用以下命名空间模式(在 JavaScript 模式中找到):

var SN = SN || ;

SN.namespace = function(ns_string) 
    var parts = ns_string.split('.'),
        parent = SN,
        i;

    // strip redundant leading global
    if(parts[0] === "SN") 
        parts = parts.slice(1);
    

    for(i = 0; i < parts.length; i++) 

        //create property if it doesn't exist
        if(typeof parent[parts[i]] === "undefined") 
            parent[parts[i]] = ;
        

        parent = parent[parts[i]];
    

    return parent;

你可以这样使用它:

SN.namespace('SN.subnamespace1.subnamespace3');
SN.subnamespace1.subnamespace3.object = function() ;

在团队工作时以这种方式创建命名空间是一种好方法 - 没有人会覆盖您的命名空间。当团队创建命名空间时,您还可以轻松地指定一个位置(文件),以便轻松查看是否使用了名称。

然后,如果需要,我将使用闭包来分隔对象的私有上下文(正如您在示例中所做的那样)。闭包可能是 JS(函数式编程)中最强大的工具。您可以创建持续存在直到删除每个内部对象的上下文。

我也喜欢getInstance JavaScript pattern 和启示模式(它适用于new,也适用于没有):

var cat = function(name, color, size) 
    var _color = color,
        _size = size,
        _name = name,
        getName = function()  return _name; ,
        getColor = function()  return _color; ,
        getSize = function()  return _size; ;
    return 
        color: getColor,
        size: getSize,
        name: getName
    
;

var new_cat1 = new cat('a', 'b', 1);
var new_cat2 = new cat('c', 'd', 2);
var new_cat3 = cat('e', 'f', 3);

console.log(new_cat1.name());
console.log(new_cat2.name());
console.log(new_cat3.name());

还阅读了我在开头提到的那本书——我个人认为它是 JS 编程时最佳实践的最佳来源。我从这本书中学到了很多东西!

【讨论】:

非常感谢。明天我一定会试一试的。 我希望它会有所帮助 - 从 Java 迁移并不容易,但肯定会很有趣。 只有一件事我还不清楚。假设我使用 [ SN.namespace('SN.cat'); 创建了一个命名空间。 ],接下来创建一个 cat 函数 [ var cat = function (... ]。如何将函数链接到命名空间?您是否建议类似 [ SN.cat = cat; ]? 我使用命名空间作为属性和方法的容器。所以我会创建命名空间SN.container,然后在这个命名空间内(在单独的文件sn.container.js中)我会这样做:SN.container.cat = function() ... 。如果您有任何其他问题 - 拍它;) 我也编辑了我的代码,因为这行有点误导:SN.namespace('SN.subnamespace1.subnamespace3.object');【参考方案2】:

就个人而言,我认为你已经做得很好了,只是一些小事情需要改进。首先,您应该将您的命名空间移动到一个单独的文件中,而不是像这样将它与您的工作放在同一个文件中。我建议你把它放在namespace.js。接下来,也很重要,你应该检查当前命名空间是否存在。您上面的代码总是声明新变量。这是一个相当严重的问题,特别是当你的同事已经声明了命名空间并为该命名空间添加了一些属性时,你的代码将完全覆盖你的同事,并且当你拉取同事的代码时总是会出错。我建议:

namespace.js:

var window.MyNameSpace = window.MyNameSpace || ;
MyNameSpace.variable = "inside mynamespace";
MyNameSpace.level1 = MyNameSpace.level1 || ;
MyNameSpace.level1.level2 = MyNameSpace.level1.level2 || ;

因此,当您的同事已经声明了命名空间或向现有命名空间添加了一些属性时,它不会覆盖现有的命名空间。假设您的同事已经创建了MyNameSpace 并添加了MyNameSpace.level1 = something。上面的代码不会覆盖MyNameSpaceMyNameSpace.level1,它只是指向当前现有的命名空间及其属性。

注意你必须在其他js文件之前包含namespace.js,否则你会得到undefined错误。

希望对您有所帮助!

【讨论】:

感谢您的详细回答,这对您有很大帮助。明天我将根据你的回答测试一些东西......在你看来,你会考虑使用 requireJS 来代替命名空间吗? (请记住,我们将在我们的项目中使用 jQuery) 我认为在这种情况下你不需要使用 requireJS,因为它只是创建命名空间,而不是加载许多依赖项的复杂 js 结构。也无需为您的页面添加更多的 16kb。【参考方案3】:

我可以看到一些快速的问题。

考虑一下:

var car = ;
car.manufacturer = "Toyota";
car.doors = 4;
car.format = "sedan";
car.wheels = 4;

这里有很多重复。 如果您要尝试将其设为进一步嵌套的对象,但继续以这种方式定义属性,则需要大量重新键入或复制/粘贴。

var garage = ;
garage.cars = [];
garage.cars[0] = ;
garage.cars[0].doors = 4;
garage.cars[0].drivers = [];
garage.cars[0].drivers[0].name = "Bob";

...等等。

这里的第一个问题是很容易错过任何形式的细节。有太多的行,太多的重复,也许在第 37 行,你忘记初始化 cars[3].drivers[1].license_number

第二个问题是,如果你打破了这个初始化,但尝试以定义不明确的顺序进行初始化(即:对多个 JS 文件执行此操作,或对多个 AJAX 调用执行此操作),那么您运行当您尝试将属性添加到尚不存在的成员时,可能会遇到异常。

// 核心js var MyApp = ; MyApp.modules = ;

// js #2 在 js #1 之前加载 MyApp.modules.myModule.subModule = ; // 会抛出异常

// js #1 MyApp.modules.myModule = ;

有很多方法可以解决这个问题,但它们都取决于您希望实现的目标:

如果您正在考虑构建具有静态默认值的数据结构,那么我建议您构建一个像这样的简单数据结构:

var car = 
    doors : 4,
    manufacturer : "Toyota"
;

或者像这样更精细的对象:

var garage = 
    cars : [
        
            doors : 2,
            drivers : [
                 name : "Bob", age : 32 ,
                 name : "Susanne", age : 18 
            ]
        
    ]
;

它们仍然可以像这样轻松引用:

garage.cars[0].drivers[1].name; // Susanne

如果您正在构建功能模块,例如库,那么命名空间仍然是一个好主意。 以其他答案中提供的任何一种方式执行此操作。

系统检查:

if (MyApp && MyApp.modules.myModule) 
    MyApp.modules.myModule.subModule = 
        send : function ()  
    ;

或者构建一个你可以使用的函数:

namespace("MyApp.modules.myModule.subModule.addOn",  method : function () ,
                                                      property_1 : 1,
                                                      property_2 : false       );

然后可以拆分字符串并检查每个名称是否存在,如果不存在则创建它们。

另外,不要使用document.write。浏览器现在可以很好地防止它搞砸网站,但您应该使用 console.log 进行日志记录,或使用 DOM 操作(或 .innerHTML,如果需要)来写入页面。

另外,that 并没有真正做到你认为的那样。this 的问题是,如果你在另一个函数内部有一个函数,this 将变为window,而不是和以前一样。

一个例子:

function decrease_fuel ()  this.fuel -= 1; 

var car = 
    fuel : 100,
    speed : 10,
    pos  :  long : ..., lat : ... ,
    dir  :  long : ..., lat : ... ,
    drive : function () 
        if (this.fuel > 0) 
            this.pos.long += this.dir.long * this.speed;
            this.pos.lat  += this.dir.lat  * this.speed;
            decrease_fuel(); // whoops! `this === window`
            // decrease_fuel.call(this); would work
        
    
;

因此,如果您要创建一个函数,希望内部作用域指向与外部相同的this,您将使用一个变量来保存外部this 的值,用于内部通过闭包引用。

var outer = function () 
    var that = this,
        inner = function ()  console.log(that); ;
    inner();
;

outer.call( name : "Bob" ); // logs bob object

如果inner 中的行是console.log(this);,那么:

outer.call( name : "Bob" ); // would log `window` object

至于namespacingrequireJS,在某种程度上,它们是同一回事。 它们的不同之处在于它们的用途。requireJS 旨在用于加载多个文件,并确保它们以正确的顺序加载。如果您使用 requireJS 定义命名空间,那么它们将在加载完成后被命名空间。

使用简单的命名空间函数,您可以按照项目约定的方式加载单个组件,然后在每个文件中使用命名空间函数添加每个模块。

如果您只打算拥有一个 JS 文件,那么 requireJS 不会比另一个提供太多好处。 如果您要拥有多个 JS 文件(每个模块一个,就像在 Java 中拆分类一样),那么requireJS 会带来好处,但需要您学习接口。

【讨论】:

以上是关于JavaScript 命名空间——寻找一种简单有效的方法的主要内容,如果未能解决你的问题,请参考以下文章

javascript 命名空间React组件的简单方法

我应该如何定义一个 JavaScript 的“命名空间”来满足 JSLint?

解析 XML 命名空间?

命名空间、“this”和库的 javascript 问题

javascript“命名空间”的费曼输出[原创]

3.1 命名空间using的声明