第9章 JavaScript设计模式
在本章中,我们将探索一些经典与现代设计模式的JavaScript实现。
开发人员通常想知道他们是否应该在工作中使用一种“理想”的模式或模式集。这个问题没有明确的唯一答案,我们研究的每个脚本和 Web 应用程序可能都有它自己的个性化需求,我们需要思考模式的哪些方面能够为实现提供实际价值。
例如,一些项目可能会受益于观察者模式提供的解耦好处(这可以减少应用程序的某些部分对彼此的依赖度),而有些项目可能只是因为太小,而根本无需考虑解耦。
也就是说,一旦我们牢牢掌握了设计模式和与它们最为相配的具体问题,那么将它们集成到我们的应用程序架构中就会变得更加容易。
在本节中将要探索的模式包括:
Constructor(构造器)模式;
Module(模块)模式;
Revealing Module(揭示模块)模式;
Singleton(单例)模式;
Observer(观察者)模式;
Mediator(中介者)模式;
Prototype(原型)模式;
Command(命令)模式;
Facade(外观)模式;
Factory(工厂)模式;
Mixin(混入)模式;
Decorator(装饰者)模式;
Flyweight(享元)模式。
9.1 Constructor(构造器)模式
在经典面向对象编程语言中,Constructor是一种在内存已分配给该对象的情况下,用于初始化新创建对象的特殊方法。在JavaScript中,几乎所有东西都是对象,我们通常最感兴趣的是object构造器。
Object构造器用于创建特定类型的对象—准备好对象以备使用,同时接收构造器可以使用的参数,以在第一次创建对象时,设置成员属性和方法的值(见图9-1)。
9.1.1 对象创建
在JavaScript中,创建新对象的两种常用方法如下所示:
//下面每种方式都将创建一个新的空对象
var newObject = {};
// object构造器的简洁记法
var newObject = new Object();
在Object构造器为特定的值创建对象封装,或者没有传递值时,它将创建一个空对象并返回它。
有四种方法可以将键值赋值给一个对象:
// ECMAScript 3兼容方式
// 1. “点”语法
// 设置属性
newObject.someKey = "Hello World";
// 获取属性
var key = newObject.someKey;
// 2. 中括号语法
// 设置属性
newObject["someKey"] = "Hello World";
// 获取属性
var key = newObject["someKey"];
// 只适用ECMAScript 5 的方式
// 3. Object.defineProperty
// 设置属性
Object.defineProperty(newObject, "someKey", {
value: "for more control of the property's behavior",
writable: true,
enumerable: true,
configurable: true
});
// 如果上面的看着麻烦,可以使用下面的简便方式
var defineProp = function (obj, key, value) {
config.value = value;
Object.defineProperty(obj, key, config);
};
// 使用上述方式,先创建一个空的person对象
var person = Object.create(null);
// 然后设置各个属性
defineProp(person, "car", "Delorean");
defineProp(person, "dateOfBirth", "1981");
defineProp(person, "hasBeard", false);
//4.Object.defineProperties
// 设置属性
Object.defineProperties(newObject, {
"someKey": {
value: "Hello World",
writable: true
},
"anotherKey": {
value: "Foo bar",
writable: false
}
});
// 可以用1和2中获取属性的方式获取3和4方式中的属性
正如我们将在本书稍后看到的,这些方法甚至可以用于继承,如下所示:
// 用法
// 创建赛车司机driver对象,继承于person对象
var driver = Object.create(person);
// 为driver设置一些属性
defineProp(driver, "topSpeed", "100mph");
// 获取继承的属性
console.log(driver.dateOfBirth);
// 获取我们设置的100mph的属性
console.log(driver.topSpeed);
9.1.2 基本Constructor(构造器)
正如我们在前面所看到的,JavaScript不支持类的概念,但它确实支持与对象一起用的特殊constructor(构造器)函数。通过在构造器前面加 new 关键字,告诉JavaScript像使用构造器一样实例化一个新对象,并且对象成员由该函数定义。
在构造器内,关键字this引用新创建的对象。回顾对象创建,基本的构造器看起来可能是这样的:
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
}
// 用法
// 可以创建car的新实例
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
// 打开浏览器控制台,查看这些对象上调用的toString()方法的输出
console.log(civic.toString());
console.log(mondeo.toString());
上面是一个简单的构造器模式版本,但它确实有一些问题。其中一个问题是,它使继承变得困难,另一个问题是,toString()这样的函数是为每个使用Car构造器创建的新对象而分别重新定义的。这不是最理想的,因为这种函数应该在所有的Car类型实例之间共享。
值得庆幸的是,因为有很多ES3和ES5兼容替代方法能够用于创建对象,所以很容易解决这个限制问题。
9.1.3 带原型的Constructor(构造器)
JavaScript中有一个名为prototype的属性。调用JavaScript构造器创建一个对象后,新对象就会具有构造器原型的所有属性。通过这种方式,可以创建多个Car对象,并访问相同的原型。因此我们可以扩展原始示例,如下所示:
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
}
// 注意这里我们使用Object.prototype.newMethod而不是Object.prototype是为了避免重新定义prototype对象
Car.prototype.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
//用法:
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString());
console.log(mondeo.toString());
现在toString()的单一实例就能够在所有Car对象之间共享。