原文: https://addyosmani.com/resources/essentialjsdesignpatterns/book/#prototypepatternjavascript
在 GoF 中对于原型模式的定义是一种通过基于已有对象克隆已有对象为模板来创建对象的模式。
我们可以认为原型模式是基于原型继承的,在原型继承中,我们创建了作为其他对象原型的对象。原型对象本身被有效的用作构造器创建对象的蓝图。如果使用的构造器函数的原型包含有一个名为 name
的属性(正如下面展示的示例),那么所有使用相同构造器创建的对象也都有这个相同的属性。
回顾现有(非 JavaScript)资料对这个模式的定义,我们可能会再次找到对类的提及。事实上是原型继承完全避免使用类。理论上是既没有“定义”对象或者核心的对象。我们只是简单的创建了现有功能对象副本。
使用原型对象的一个好处就是我们可以利用 JavaScript 本身的原型优势,而不是模仿其他语言的功能。在其他设计模式中,情况并非总能如此。
这个模式是不仅一个实现继承的简单方式,它还能带来性能的提升:在对象中定义一个方法时,它们都是通过引用创建(这样所有的子对象都指向相同的函数)而不是创建它们独立的副本。
对于那些感兴趣的,真正的原型继承,如 ECMAScript 5 中定义的,需要使用 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 );
// 现在我们可以看出 myCar 是 yourCar 的原型
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.defineProperties
和 Object.defineProperty
方法参数的语法。
要注意的是,原型关系在枚举对象属性和(如 Crockford 建议的)使用 hasOwnProperty()
检查包装循环内容时会造成麻烦。
如果我们希望不直接使用 Object.create
来声明原型模式,我们可以用下面这个方式来模拟出像上面例子中的模式。
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 可能在没注意的情况下被修改) 。
最终的原型模式替代方案如下:
var beget = (function () {
function F() {}
return function ( proto ) {
F.prototype = proto;
return new F();
};
})();
可以从 vehicle
函数引用这个方法。注意,然而这里的 vehicle
是模拟一个构造器,因为原型模式除了将对象链接到原型之外,不包含任务初始化的概念。