# 原型模式
原型模式(Prototype):用原型实例指向创建对象的类,使用于创建新的对象的类共享原型对象的属性以及方法。
🌰 假设页面中有很多轮播图, 那么我们要实现这些轮播图最好的方式就是通过创建对象来一一实现,所以我们就需要有一个轮播图类,比如我们把这个类定义为 LoopImages
。
// 图片轮播类
var LoopImages = function(imgArr, container){
this.imagesArray = imgArr; // 轮播图片数组
this.container = container; // 轮播图片容器
this.createImage = function(){} // 创建轮播图片
this.changeImage = function(){} // 切换下一张图片
}
如果一个页面中有多个这类焦点图,其切换动画一般是多样化的,有的可能是上下切换,有的可能是左右切换,有的可能是渐隐切换,有的可能是放缩切换,等等. 因此创建的轮播图片结构应该是多样化的,同样切换的效果也应该是多样化的,因此我们应该抽象出一个基类,让不同特效类去继承这个基类,然后对于差异化的需求通过重写这些继承下来的属性或者方法来解决。当然不同的子类之间可能存在不同的结构样式,比如有的包含一个左右切换箭头,于是我们有了下面的例子:
// 上下滑动切换类
var SlideLoopImg = function(imgArr, container){
// 构造函数继承图片轮播类
LoopImages.call(this, imgArr, container);
// 重写继承的切换下一张图片方法
this.changeImage = function(){
console.log('SlideLoopImg changeImage function');
}
}
// 渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow){
LoopImages.call(this, imgArr, container);
// 切换箭头私有变量
this.arrow = arrow;
this.changeImage = function(){
console.log('FadeLoopImg changeImage function');
}
}
// 实例化一个渐隐切换图片类
var fadeImg = new FadeLoopImg([
'01.jpg',
'02.jpg',
'03.jpg',
'04.jpg'
],'slide', [
'left.jpg',
'right.jpg'
]);
fadeImg.changeImage(); // FadeLoopImg changeImage function
# 优化
我们再看基类 LoopImages
,作为基类是要被子类继承的,那么此时将属性和方法都写在基类的构造函数里会有一些问题,比如每次子类继承都要创建一次父类,假如说父类的构造函数中创建时存在很多耗时较长的逻辑,或者说每次初始化都做一些重复性的东西,这样的性能消耗还是蛮大的。
为了提高性能,我们需要有一种共享机制,这样每当创建基类时,对于每次创建的一些简单而又差异化的属性我们可以放在构造函数中,而我们将一些消耗资源比较大的方法放在基类的原型中,这样就会避免很多不必要的消耗,这也就是原型模式的一个雏形。
// 图片轮播类
var LoopImages = function(imgArr, container){
this.imagesArray = imgArr; // 轮播图片数组
this.container = container; // 轮播图片容器
}
LoopImages.prototype = {
// 创建轮播图片
createImage : function(){
console.log('LoopImages createImage function');
},
// 切换下一张图片
changeImage : function(){
console.log('LoopImages changeImage function');
}
}
// 上下滑动切换类
var SlideLoopImg = function(imgArr, container){
// 构造函数继承图片轮播类
LoopImages.call(this, imgArr, container);
}
SlideLoopImg.prototype = new LoopImages();
// 重写继承的切换下一张图片方法
SlideLoopImg.prototype.changeImage = function(){
console.log('SlideLoopImg changeImage function');
}
// 渐隐切换类
var FadeLoopImg = function(imgArr, container, arrow){
LoopImages.call(this, imgArr, container);
// 切换箭头私有变量
this.arrow = arrow;
}
FadeLoopImg.prototype = new LoopImages();
FadeLoopImg.prototype.changeImage = function(){
console.log('FadeLoopImg changeImage function');
}
// 测试用例
console.log(fadeImg.container); // slide
fadeImg.changeImage(); // FadeLoopImg changeImage function
# 原型继承
原型模式更多的是用在对象的创建上。比如创建一个实例对象的构造函数比较复杂,或者耗时比较长,或者通过创建多个对象来实现。此时我们最好不要用 new
关键字去复制这些基类,但可以通过对这些对象属性或者方法进行复制来实现创建,这是原型模式的最初思想。如果涉及多个对象,我们也可以通过原型模式来实现对新对象的创建。那么首先要有一个原型模式的对象复制方法。
/********
* 基于已经存在的模板对象克隆出新对象的模式
* arguments[0], arguments[1], arguments[2]: 参数1,参数2,参数3 表示模板对象
* 注意。这里对模板引用类型的属性实质上进行了浅复制(引用类型属性共享),当然根据需求可以自行深复制(引用类型属性复制)
*****/
function prototypeExtend(){
var F = function(){}, // 缓存类,为实例化返回对象临时创建
args = arguments, // 模板对象参数序列
i = 0,
len = args.length;
for(; i < len; i++){
// 遍历每个模板对象中的属性
for(var j in args[i]){
// 将这些属性复制到缓存类原型中
F.prototype[j] = args[i][j];
}
}
// 返回缓存类的一个实例
return new F();
}
比如企鹅游戏中我们创建一个企鹅对象,如果游戏中没有企鹅基类,只是提供了一些动作模板对象,我们就可以通过实现对这些模板对象的继承来创建一个企鹅实例对象。
var penguin = prototypeExtend({
speed : 20,
swim : function(){
console.log('游泳速度 ' + this.speed);
}
},{
run : function(speed){
console.log('奔跑速度 ' + speed);
}
},{
jump : function(){
console.log('跳跃动作');
}
})
既然通过 prototypeExtend
创建的是一个对象,我们就无需再用 new
去创建新的实例对象,我们可以直接使用这个对象
penguin.swim(); // 游泳速度 20
penguin.run(10); // 奔跑速度 10
penguin.jump(); // 跳跃动作
# 小结
『 原型模式 』就是将可复用的、可共享的、耗时大的从基类中提出来然后放在其原型中,然后子类通过组合继承或者寄生组合式继承而将方法和属性继承下来,对于子类中那些需要重写的方法进行重写,这样子类创建的对象既具有子类的属性和方法也共享了基类的原型方法
原型对象更适合在创建复杂的对象时,对于那些需求一直在变化而导致对象结构不停地改变时,将那些比较稳定的属性与方法共用而提取的继承的实现