JavaScript学习笔记(二十一) 对象创建模式-命名空间模式

来源:互联网 发布:ipad美女直播软件 编辑:程序博客网 时间:2024/05/16 11:35

在JavaScript中创建对象是非常简单的,你可以使用对象字面量(object literal)或者使用构造函数(方法)(constructor)。

在这章里,我们会超出这些,看到一些额外的对象创建模式。

JavaScript语言是简单而直接的,通常没有特别的语法实现你在其它语言中使用的功能,比如命名空间(namespaces)、模块(modules)、包(packages),私有属性(private properties)和静态变量(static members)。接下来,我们会去实现这些常见的模式,或者使用其他方式代替,或者比较二者的不同。

我们会看到命名空间(namespacing),依赖声明(dependency declaration),模块模式(module pattern)和沙箱模式(sandbox patterns),这些会帮助组织和构建你的程序代码并且减少潜在的全局变量。讨论的主题还包括private或者享有特权(privileged)的成员变量,static或者private static的成员,对象常量,链式编程,使用特殊的方法的构造函数。

命名空间模式(Namespace Pattern)

命名空间会帮助减少程序需要的全局变量的数量,同时也帮助我们防止命名冲突或者过多的命名前缀(加前缀为了防止命名冲突)。
JavaScript语法本身没有内置的命名空间语法,但这个功能是十分容易实现的。为了防止大量的函数,对象或者其它变量污染全局作用域,你可以为你的程序或类库创建一个(理想情况只有一个)全局对象。然后你可以给这个对象添加所有的功能性需求。
像下面这个例子:
// BEFORE: 5 globals// Warning: antipattern// constructorsfunction Parent() {}function Child() {}// a variablevar some_var = 1;// some objectsvar module1 = {};module1.data = {a: 1, b: 2};var module2 = {};
你可以重构这种类型的代码通过为你的程序创建一个全局对象,比如叫做:MYAPP,并且将你所有的函数和变量作为这个全局变量的属性:
// AFTER: 1 global// global objectvar MYAPP = {};// constructorsMYAPP.Parent = function () {};MYAPP.Child = function () {};// a variableMYAPP.some_var = 1;// an object containerMYAPP.modules = {};// nested objectsMYAPP.modules.module1 = {};MYAPP.modules.module1.data = {a: 1, b: 2};MYAPP.modules.module2 = {};
对于全局命名空间对象的名字,你可以选择你的程序或者类库的名字,你的域名,或者公司的名字。通常程序猿都会遵循约定,全局变量名全大写,所以更容易被代码的读者发现。(但也要记住全部大写通常也被用来命名常量)
这种模式是给你的代码添加命名空间和防止命名冲突的好方法,不仅仅可以防止你自己代码中的命名冲突,也可以防止你的代码和第三方代码命名冲突,比如JavaScript类库或控件。
这种模式被极力推荐并且可以完美适用于很多任务,但它也有一些不足之处:
  • 需要更多的输入;每个变量和函数都要添加一个前缀,会增加需要下载的代码量。
  • 只有一个全局变量意味着任何地方的代码都可以修改这个全局变量,其它地方都会受到影响。
  • 很长的嵌套名称会降低属性查找速度。
后面说到沙箱模式会解决这些不足。

通用的命名空间函数(General Purpose Namespace Function)

随着程序复杂度的增加,有部分代码会被分开放到不同的文件中,然后有条件的被包含(included),假设你的代码是第一个声明某个命名空间或者它的属性是不安全的;
有些你正在添加的属性可能已经存在了,并且你可能覆盖了它们。
因此,在添加一个属性或者创建一个命名空间之前,最好先检查一下它是否已经存在,就像下面这个例子一样:
// unsafevar MYAPP = {};// betterif (typeof MYAPP === "undefined") {    var MYAPP = {};}// or shortervar MYAPP = MYAPP || {};
你可以看到这些添加的检查会很快导致大量重复代码。比如:如何你想定义MYAPP.modules.module2,你讲不得不进行三次检查,每个你定义的属性或对象都需要一次;
这就是为什么你需要一个方便的可重用的函数来具体维护命名空间。让我们将这个函数叫做namespace() 并且向这样去使用:
// using a namespace functionMYAPP.namespace('MYAPP.modules.module2');// equivalent to:// var MYAPP = {//     modules: {//         module2: {}//     }// };
接下来的例子是命名空间函数的实现。这个实现是非破坏性的,意味着如何一个命名空间已经存在,那么它不会被重新创建:
var MYAPP = MYAPP || {};MYAPP.namespace = function (ns_string) {    var parts = ns_string.split('.'),        parent = MYAPP,        i;    // strip redundant leading global    if (parts[0] === "MYAPP") {        parts = parts.slice(1);    }    for (i = 0; i < parts.length; i += 1) {        // create a property if it doesn't exist        if (typeof parent[parts[i]] === "undefined") {            parent[parts[i]] = {};        }        parent = parent[parts[i]];    }    return parent;};
这个实现可以这样使用:
// assign returned value to a local varvar module2 = MYAPP.namespace('MYAPP.modules.module2');module2 === MYAPP.modules.module2; // true// skip initial `MYAPP`MYAPP.namespace('modules.module51');// long namespaceMYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');