JavaScript命名空间 - 寻找简单有效的方法

时间:2013-04-02 03:42:32

标签: javascript jquery javascript-framework

我想在项目中实现JavaScript Namespacing。我的同事和我不是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>

===问题===

Q1。你认为这是一个好方法吗?

Q2。我们可以遇到任何陷阱吗?

Q3。你有更好的建议吗(请考虑简单性,可读性)?

非常感谢任何帮助。

干杯,

修改 我们将使用jQuery。所以,你有一个更好的方法,我们会考虑使用jQuery。

3 个答案:

答案 0 :(得分:2)

即使是简单的项目,我总是使用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编程时的最佳良好实践来源。我从这本书中学到了很多东西!

答案 1 :(得分:0)

就个人而言,我认为你已经做得很好,只是需要改进一些小事。首先,您应该将命名空间移动到单独的文件中,而不是将其与您的工作放在同一个文件中。我建议你把它放在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错误。

希望这有帮助!

答案 2 :(得分:0)

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

考虑一下:

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,如果需要)来写入页面。< / p>

同样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会带来好处,代价是要求你学习界面。