本节书摘来自异步社区《JavaScript设计模式》一书中的第9章,第9.7节, 作者: 【美】Addy Osmani 译者: 徐涛 更多章节内容可以访问云栖社区“异步社区”公众号查看。
9.7 Prototype(原型)模式
“四人组”称Prototype模式为一种基于现有对象模板,通过克隆方式创建对象的模式。
我们可以认为Prototype模式是基于原型继承的模式,可以在其中创建对象,作为其他对象的原型。prototype对象本身实际上是用作构造函数创建每个对象的蓝图。如果所用构造函数的原型包含一个名为name的属性(代码样例如下),那么由同样的构造函数创建的每个对象也会有同样的属性(见图9-6)。
如果查看现有(非JavaScript)文献对该模式的定义,我们可能会再一次发现对类的引用。现实情况是,原型继承避免和类(Class)一起使用。理论上没有“定义”的对象,也没有核心的对象。我们仅是创建现有功能对象的拷贝。
使用Prototype模式的其中一个好处是,我们获得的是JavaScript其本身所具有的原型优势,而不是试图模仿其他语言的特性。与其他设计模式一起使用时,情况并非总是如此。
模式不仅是一种实现继承的简单方法,它也可以带来性能提升:在一个对象中定义一个函数,它们都是由引用创建(因此所有子对象都指向相同的函数),而不是创建它们自己的单份拷贝。
如同ECMAScript5标准所定义的,那些有趣的、真正的原型继承要求使用Object.create(我们在本节的前面曾看到过)。Object.create创建一个对象,拥有指定原型和可选的属性(例如Object.create(prototype, optionalDescriptorObjects))。
在下面的示例中可以看到它的演示:
var myCar = {
name: "Ford Escort",
drive: function () {
console.log("Weeee. I'm driving!");
},
panic: function () {
console.log("Wait. How do you stop this thing?");
}
};
// 使用Object.create实例化一个新car
var yourCar = Object.create(myCar);
// 现在可以看到一个对象是另外一个对象的原型
console.log(yourCar.name);
Object.create还允许我们轻松实现差异继承等高级概念,通过差异继承,对象可以直接继承自其他对象。我们之前已经了解到,Object.create允许我们使用第二个提供的参数来初始化对象属性。例如:
var vehicle = {
getModel: function () {
console.log("The model of this vehicle is.." + this.model);
}
};
var car = Object.create(vehicle, {
"id": {
value: MY_GLOBAL.nextId(),
// writable:false, configurable:false 默认值
enumerable: true
},
"model": {
value: "Ford",
enumerable: true
}
});
在这里,可以使用对象字面量在Object.create的第二个参数上初始化属性,并且对象字面量采用的语法与Object.defineProperties和Object.defineProperty方法所使用的语法相似,我们之前已经了解过这些方法。
值得注意的是,在枚举对象的属性以及在hasOwnProperty()检查中包装循环内容时(Crockford推荐),原型关系可能会引起麻烦。
如果希望在不直接使用Object.create的情况下实现Prototype模式,我们可以按照上面的示例模拟该模式,如下所示:
var vehiclePrototype = {
init: function (carModel) {
this.model = carModel;
},
getModel: function () {
console.log("The model of this vehicle is.." + this.model);
}
};
function vehicle(model) {
function F() { };
F.prototype = vehiclePrototype;
var f = new F();
f.init(model);
return f;
}
var car = vehicle("Ford Escort");
car.getModel();
这个方案不允许用户以同样的方式定义只读属性(因为如果不小心,vehiclePrototype可能会被改变)。
最后一种可供选择的Prototype模式实现可以是这样的:
var beget = (function () {
function F() { }
return function (proto) {
F.prototype = proto;
return new F();
};
})();
我们可以从vehicle函数引出这个方法。请注意,这里的vehicle模仿了一个构造函数,因为Prototype模式不包含任何初始化的概念,而不仅是将对象链接至原型。