《JavaScript模式》第5章 对象创建模式

@by Ruth92(转载请注明出处)

第5章:对象创建模式

JavaScript 是一种简洁明了的语言,并没有其他语言中经常使用的一些特殊语法特征,如 命名空间模块私有属性 以及 静态成员 等语法。

但是通过常见的模式,可以实现、替换其他语言中的语法特征。

1. 命名空间模式

// 全局变量,不安全
var MYAPP = {};

// 更好的代码风格
if (typeof MYAPP === 'undefined') {
    var MYAPP = {};
}

// 或者用更短的语句
var MYAPP = MYAPP || {};

// 构造函数
MYAPP.Parent = function() {};
MYAPP.Child = function() {};

// 一个变量
MYAPP.some_var = 1;

// 一个对象容器
MYAPP.modules = {};

// 嵌套对象
MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {a: 1, b: 2};
MYAPP.modules.module2 = {};

【通用命名空间函数实现示例】:

var MYAPP = MYAPP || {};

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

    // 剥离最前面的冗余全局变量
    if (parts[0] === 'MYAPP') {
        parts = parts.slice(1);
    }

    for (i = 0; i < parts.length; i++) {
        // 如果它不存在,就创建一个属性
        if (typeof parent[parts[i]] === 'undefined') {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }

    return parent;
}

// 测试:
// 将返回值赋给一个局部变量
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2;  // true

// 忽略最前面的'MYAPP'
MYAPP.namespace('modules.module51');

优点:有助于减少程序中所需要的全局变量的数量,并且同时还有助于避免冲突或过长的名字前缀。

缺点:

  • 需要输入更多的字符,变量加前缀,增加代码量;
  • 仅有一个全局实例,任意部分的代码都可以修改它;
  • 长嵌套名字,更长的属性解析查询时间。

解决方案:沙箱模式

2. 声明依赖模式

var myFunction = function() {
  // 依赖
  var event = YAHOO.util.Event,
      dom = YAHOO.util.Dom;

  // 使用事件和DOM变量
  // 下面的函数...
}

优点:

  • 显示的依赖声明表明了特定脚本文件已经包含在该页面中;
  • 在函数顶部的前期声明可以很容易发现并解析依赖;
  • 解析局部变量的速度总是要比解析全局变量(或全局变量的嵌套属性)快,提升性能;
  • 利用工具可以重命名局部变量,会生成更小的代码量。

3. 私有模式

JavaScript 中所有对象的成员是公共的。

私有成员:利用 闭包 实现。

特权方法:指可以访问私有成员的公共方法,因为它具有访问私有属性的“特殊”权限。

私有性失效:从特权方法中返回一个私有变量,且该变量恰好是一个对象或者数组,那么外面的代码仍然可以访问该私有变量,因为它通过引用传递。

1)使用构造函数获得私有性

/**
 * 利用闭包实现私有成员
 */
function Gadget() {
  // 私有成员
  var name = "iPod";
  var color = ['red'];

  // 公有函数
  this.getName = function() {
    return name;
  };

  // 反模式
  // 不要传递需要保持私有性的对象和数组的引用
  this.getColor = function() {
    return color;
  };

}

var toy = new Gadget();

console.log(toy.name); // 'undefined'
console.log(toy.getName());  // 'iPod'

var newColor = toy.getColor();
newColor[0] = 'black';
console.log(toy.getColor());  // 'black'

缺点:当将私有成员与构造函数一起使用时,每次调用构造函数以创建对象时,这些私有成员都会被重新创建。

解决方案:使用原型共享常用属性和方法,另外,还可以在多个实例中共享隐藏的实例成员。

2)模块模式的基础框架:对象字面量以及私有性
/**
 * 使用对象字面量+匿名函数实现私有成员
 */
var myobj;
(function() {
  // 私有成员
  var name = 'my, oh my';

  // 实现公有部分
  // 注意,没有 'var' 修饰符
  myobj = {
    // 特权方法
    getName: function() {
      return name;
    }
  }
}());

myobj.getName();  // 'my, oh my'

/**
 * 实现2
 */
var myobj = (function() {
  // 私有成员
  var name = 'my, oh my';

  // 实现公有部分
  return {
    getName: function() {
      return name;
    }
  }
}());

myobj.getName();  // 'my, oh my'
3)原型和私有性

优点:解决构造函数获得私有性的缺点。

function Gadget() {
  // 私有成员
  var name = 'iPod';
  // 公有函数
  this.getName = function() {
    return name;
  };
}

Gadget.prototype = (function() {
  // 私有成员
  var browser = "Mobile Webkit";
  // 公有原型成员
  return {
    getBrowser: function() {
      return browser;
    }
  };
}());

var toy = new Gadget();
toy.getName();  // 特权 'own' 方法
toy.getBrowser(); // 特权原型方法
4)将私有方法揭示为公共方法

揭示模式 可用于将私有方法暴露成公共方法。

优点:当为了对象的运转而将所有功能放置在一个对象中以及想尽可能地保护该对象是至关重要的时候,这种揭示模式就显得非常有用。

缺点:将这又私有方法暴露为公共方法时,也使它们变得更为脆弱。

/**
 * 它建立在其中一种私有模式之上,即对象字面量中的私有成员
 */
var myarray;
(function() {
  var astr = '[object Array]',
      toString = Object.prototype.toString;

  function isArray(a) {
    return toString.call(a) === astr;
  }

  function indexOf(haystack, needle) {
    var i = 0,
        max = haystack.length;
    for (; i < max; i += 1) {
      if (haystack[i] === needle) {
        return i;
      }
    }
    return -1;
  }

  myarray = {	// 填充了认为适于公共访问的功能
    isArray: isArray,
    indexOf: indexOf,	// 如果公共indexOf方法发生意外,私有indexOf方法仍然是安全的
	inArray: indexOf	// inArray()将继续正常运行
  };
}());

4. 模块模式

  • JavaScript 并没有包的特殊语法,但是模块模式提供了一种创建自包含非耦合代码片段的有利工具,可以将它视为黑盒功能,并可以根据需求添加、替换或删除这些模块。

  • 模块模式是多种模式的组合:命名空间、即时函数、私有和特权成员、声明依赖。

优点:提供了结构化的思想并有助于组织日益增长的代码。

// 第一步:建立一个命名空间
MYAPP.namespace('MYAPP.utilities.array');

// 第二步:定义该模块
MYAPP.utilities.array = (function() {

      // 依赖
  var uobj = MYAPP.utilities.object,
      ulang = MYAPP.utilities.lang,

      // 私有属性
      array_string = '[object Array]',
      ops = Object.prototype.toString;

      // 私有方法
      // ...

      // var 变量定义结束

  // 可选的一次性初始化过程
  // ...

  // 第三步:向公共接口添加一些方法
  // 公有API
  return {
    return {
      inArray: function(needle, haystack) {
        // ...
      },
      isArray: function(a) {
        // ...
      }
      // ... 更多方法和属性
    };
  }
}());
揭示模块模式

——其中所有的方法都需要保持私有性,并且只能暴露那些最后决定设立API的那些方法

MYAPP.utilities.array = (function() {

  // 私有属性
  // 私有方法
  isArray = function() {

  },
  inArray = function() {

  };

  // 揭示公有API
  return {
    isArray: isArray,
    inArray: inArray
  }
})();
创建构造函数的模块

与使用模式模式来执行创建对象的操作的区别:包装了模块的即时函数最终会返回一个函数,而不是返回一个对象。

/**
 * 创建构造函数的模块
 */
MYAPP.utilities.array = (function() {

  // 私有属性和方法
  var Constr;

  // 公有API——构造函数
  Constr = function(o) {
    this.elements = this.toArray(0);
  };
  
  // 公有API——原型
  Constr.prototype = {
    constructor: MYAPP.utilities.Array,
    version: '2.0',
    toArray: function(obj) {
      for (var i = 0, a = [], len = obj.length; i < len; i += 1) {
        a[i] = obj[i];
      }
      return a;
    }
  }

  // 返回要分配给新命名空间的构造函数
  return Constr;
})();

// 使用
var arr = new MYAPP.utilities.Array(obj);
将全局变量导入到模块中

导入全局变量有助于加速即时函数中的全局符号解析的速度,因为这些导入的变量成为了该函数的局部变量。

MYAPP.utilities.module = (function(app, global) {

  // 引用全局变量
  // 以及现在被转换成局部变量的
  // 全局应用程序命名空间对象

}(MYAPP, this));

5. 沙箱模式

优点:解决命名空间模式的缺点。提供了一个可用于模块运行的环境,且不会对其他模块和个人沙箱造成任何影响。


静态属性和方法:指那些从一个实例到另一个实例都不会发生改变的属性和方法。

公有静态成员的实现:使用构造函数并向其添加属性。

私有静态成员:1)以同一个构造函数创建的所有对象共享该成员;2)构造函数外部不可访问该成员。

对象常量:JavaScript 使用命名规定。

/**
 * 多次实例化沙箱对象的方法:
 */
new SandBox(function(box) {
  // 你的代码写在这里...
});

SandBox(['ajax', 'event'], function(box) {
  // console.log(box);
});

SandBox('ajax', 'dom', function(box) {
  // console.log(box);
});

SandBox('*', function(box) {
  // console.log(box);
});

SandBox(function(box) {
  // console.log(box);
});


//===============================
/**
 * 将一个模块嵌入到另一个模块中,且这两者之间不会相互干扰
 */
SandBox('dom', 'event', function(box) {
  // 使用 DOM 和事件来运行
  Sandbox('ajax', function(box) {
    // 另一个沙箱化(sandboxed)的'box'对象
    // 这里的'box'对象与函数外部的
    // 'box'并不相同

    //...
    // 用Ajax来处理
  });

  // 这里没有Ajax模块
})


//===============================
// 增加模块
Sandbox.modules = {};

Sandbox.modules.dom = function(box) {
  box.getElement = function() {};
  box.getStyle = function() {};
  box.foo = 'bar';
};

Sandbox.modules.event = function(box) {
  // 如果需要,就访问Sandbox原型,如下语句
  box.constructor.prototype.m = 'mmm';
  box.attachEvent = function() {};
  box.dettachEvent = function() P{;}
};

Sandbox.modules.ajax = function(box) {
  box.makeRequest = function() {};
  box.getResponse = function() {};
};
实现 Sandbox() 构造函数
function Sandbox() {
  // 将参数转换成一个数组
  var args = Array.prototype.slice.call(arguments),
    // 最后一个参数是回调函数
    callback = args.pop(),
    // 模块可以作为一个数组传递,或作为单独的参数传递
    modules = (args[0] && typeof args[0] === 'string') ? args : args[0],
    i;

    // 确保该函数
    // 作为构造函数被调用
    if (!(this instanceof Sandbox)) {
      return new Sandbox(module, callback);
    }

    // 需要向 `this` 添加的属性
    this.a = 1;
    this.b = 2;

    // 现在向该核心`this`对象添加模块
    // 不指定模块名称或指定"*"都表示"使用所有模块"
    if (!module || module === '*') {
      module = [];
      for (i in Sandbox.modules) {
        if (Sandbox.modules.hasOwnProperty(i)) {
          modules.push(i);
        }
      }
    }

    // 初始化所需的模块
    for (i=0; i<moduels.length; i++) {
      Sandbox.modules[modules[i]](this);
    }

    // call the callback
    callback(this);
}

// 需要的任何原型属性
Sandbox.prototype = {
  name: 'My Application',
  version: '1.0',
  getName: function() {
    return this.name;
  }
}
公有静态成员
/**
 * 通过使用构造函数并向其添加属性
 */
var Gadget = function(price) {
  this.price = price;
};

// 静态方法
Gadget.isShiny = function() {

  var msg = 'you bet';

  if (this instanceof Gadget) {
    msg += ', it costs $' + this.price + '!';
  }

  return msg;
}

// 向该原型添加一个普通方法
Gadget.prototype.setPrice = function() {
  return Gadget.isShiny.call(this);
}

// 测试静态方法调用
Gadget.isShiny(); // 'you bet'

// 测试实例,非静态调用
var a = new Gadget('499.99');
a.isShiny();  // 'you bet, it costs $499.99!'
私有静态成员
var Gadget = (function() {
  // 静态变量/属性
  var counter = 0,
      NewGadget;

  // 这将成为
  // 新的构造函数的实现
  NewGadget = function() {
    counter += 1;
  };

  // 特权方法
  NewGadget.prototype.getLastId = function() {
    return counter;
  }

  // 覆盖该构造函数
  return NewGadget;
}());

var iphone = new Gadget();
iphone.getLastId(); // 1
var ipod = new Gadget();
ipod.getLastId(); // 2
var ipad = new Gadget();
ipad.getLastId(); // 3

6. 链模式

var obj = {
  value: 1,
  increment: function() {
    this.value += 1;
    return this;
  },
  add: function(v) {
    this.value += v;
    return this;
  },
  shout: function() {
    console.log(this.value);
  }
};

// 链方法调用
obj.increment().add(3).shout(); // 5

优点:节省一些输入的字符,代码更简洁,可维护性高。

缺点:更加难以调试。

7. method() 方法——语法糖

var Person = function(name) {
  this.name = name;
}.
  method('getName', function() {
    return this.name;
  }).
  method('setName', function() {
    this.name = name;
    return this;
  });

if (typeof Function.prototype.method !== 'function') {
  Function.prototype.method = function(name, implementation) {
    this.prototype[name] = implementation;
    return this;
  };
}

原文地址:https://www.cnblogs.com/Ruth92/p/5937479.html