JavaScript - 原型模式

116 阅读5分钟

原型-prototype.jpeg

原型模式是一种创建型对象,他允许一个对象再创建另外一个可定制的对象,无需知道任何如何创建的细节。并且原型实例指向创建对象的类,创建出来的类可以共享继承实例的属性和方法。

它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。

function LoopImages( imgArr,container ) {
    this.imgArr = imgArr
    this.container = container
​
    // 创建轮播图片
    this.createImage = function() {
        console.log("LoopImages createImage function");
    }
    // 切换下一张图片
    this.changeImage = function() {}
}
​
function SlideLoopImage( imgArr,container ) {
    LoopImages.call( this,imgArr,container )
​
    // 重写继承的切换下一张图片方法
    this.changeImage = function() {
        console.log( "SlideLoopImage changeImage function" );
    }
}
​
function FadeLoopImage( imgArr,container,arrow ) {
    LoopImages.call( this,imgArr,container )
​
    // 切花箭头私有变量
    this.arrow = arrow
​
    // 重写继承的切换下一张图片方法
    this.changeImage = function() {
        console.log( "FadeLoopImage changeImage function" );
    }
}
​
var FadeImg = new FadeLoopImage(
    [
        '01.jpg',
        '02.jpg',
        '03.jpg',
        '04.jpg'
    ],
    'slide',
    [
        'left.jpg',
        'right.jpg'
    ]
)
​
FadeImg.createImage()
FadeImg.changeImage()

如上面的代码,我们去考虑轮播图的时候,它的切换效果可能不止一种,但是在创建轮播图的过程中,有些步骤是相同的,如果没一个过程都去写一份代码,就会导致代码过于臃肿。我们先可以实现一个 LoopImages 类让部分逻辑可以复用,并在不同的需求中改写属性和方法,此时 LoopImages 就可以当作基类来使用。

原型 prototype

上面的例子中,并不是最好的解决方案。基类 LoopImages 是需要被子类继承的,但是此时的基类属性和方法都写在一起的。比如每次子类都继承了基类,但是基类的构造函数在创建的时候消耗了大量的时间,或者说每次构造的时候都做了一些重复性的事情,就会对性能产生影响。

为了提高性能,我们需要一种共享机制,这样每当创建基类时,我们把一些简单而差异化的属性放在构造函数中,而将一些消耗支援比较大的方法放在基类的原型中,就可以避免很多没必要的消耗。

function LoopImage( imgArr,container ) {
    this.imgArr = imgArr
    this.container = container
}
​
LoopImage.prototype = {
    // 创建轮播图
    createImage:function() {
        console.log( "createImage" );
    },
​
    // 切换下一张图片
    changeImage:function() {
        console.log("changeImage");
    },
​
    getContainer:function() {
        console.log( this.container );
    }
}
​
function SlideLoopImage( imgArr,container ) {
    LoopImage.call( this,imgArr,container )
}
SlideLoopImage.prototype = new LoopImage()
// 改写继承下来切换图片的方法
SlideLoopImage.prototype.changeImage = function() {
    console.log( "SlideLoopImage changeImage function" );
}
​
function FadeLoopImage( imgArr,container,arrow ) {
    LoopImage.call( this,imgArr,container )
    this.arrow = arrow
}
FadeLoopImage.prototype = new LoopImage()
FadeLoopImage.prototype.changeImage = function() {
    console.log( "FadeLoopImage changeImage function" );
}
​
var FadeImg = new FadeLoopImage(
    [
        '01.jpg',
        '02.jpg',
        '03.jpg',
        '04.jpg'
    ],
    'slide',
    [
        'left.jpg',
        'right.jpg'
    ]
)
​
FadeImg.createImage()
FadeImg.changeImage()
FadeImg.getContainer()
  • 每一个函数数据类型( 普通函数,类 ) 天生带有 prototype ( 原型 ) 属性,这个属性是一个对象数据类型的值。
  • 并且在prototype上浏览器天生给它加了一个属性constructor(构造函数),属性值是当前函数(类)本身
  • 每一个对象数据类型(普通的对象、实例)也天生自带一个属性proto,属性值是当前实例所属类的原型(prototype)

原型扩展

原型对象是一个共享的对象,那么不论父类的实例对象或者子类的继承.都是对它的一个指向引用。所以原型才会被共享,如果对基类的原型对象进行扩展,不论时子类还是父类的实例对象都会继承下来。

LoopImage.prototype = {
    // ...
​
    getContainer:function() {
        console.log( this.container );
    }
}
​
FadeImg.getContainer() // slide

原型继承

原型模式更多的使用在对对象的创建上。比如创建一个实例对象的构造函数比较复杂,或者耗时太长,可以通过 create 或者自己来实现创建对象。

create 继承

我们可以利用JavaScript特有的原型继承特性去创建对象的方式,也就是创建的一个对象作为另外一个对象的prototype属性值。原型对象本身就是有效地利用了每个构造器创建的对象。

var LoopImage = {
    imgArr:[],
    container:"",
    createImage:function() {},
    changeImage:function() {}
}
​
var FadeLoopImage = Object.create( LoopImage )
FadeLoopImage.changeImage = function() {
    console.log( "FadeLoopImage changeImage function" );
}
​
FadeLoopImage.changeImage()

Object.create运行你直接从其它对象继承过来,使用该方法的第二个参数,你可以初始化额外的其它属性。例如:

var LoopImage = {
    imgArr:[],
    container:"",
    createImage:function() {},
    changeImage:function() {}
}
​
var FadeLoopImage = Object.create( LoopImage,{
    imgArr:[
        '01.jpg',
        '02.jpg',
        '03.jpg',
        '04.jpg'
    ],
    container:"slide",
    arrow:[
        'left.jpg',
        'right.jpg'
    ]
})
FadeLoopImage.changeImage = function() {
    console.log( "FadeLoopImage changeImage function" );
}
​
FadeLoopImage.changeImage()

这里,可以在Object.create的第二个参数里使用对象字面量传入要初始化的额外属性,其语法与Object.definePropertiesObject.defineProperty方法类似。它允许您设定属性的特性,例如enumerable, writableconfigurable

自己实现

如果你希望自己去实现原型模式,而不直接使用Object.create 。你可以使用像下面这样的代码为上面的例子来实现:

function prototypeExtend() {
    var F    = function(){},    // 缓存类,为实例化返回对象临时创建
        args = arguments,       // 模板对象参数
        i    = 0,
        len  = args.length
    
    for( ;i<len;i++ ) {
        // 遍历模板参数对象中的属性
        for( var key in args[i] ) {
            // 将这些属性复制到缓存类原型中
            F.prototype[key] = args[i][key]
        }
    }
​
    // 返回缓存类的实例
    return new F()
}
​
var FadeImage = prototypeExtend( {
    imgArr:[
        '01.jpg',
        '02.jpg',
        '03.jpg',
        '04.jpg'
    ],
    container:"slide",
    arrow:[
        'left.jpg',
        'right.jpg'
    ],
    createImage:function() {
        console.log( "FadeImage createImage function" );
    },
    changeImage:function() {
        console.log( "FadeImage changeImage function" );
    },
    getContainer:function() {
        console.log( this.container );
    }
} )
​
FadeImage.createImage()     // FadeImage createImage function
FadeImage.changeImage()     // FadeImage changeImage function
FadeImage.getContainer()    // slide

总结

原型模式可以让多个对象分享同一个原型对象的属性和方法,这也是一种继承方式,不过这种继承是不需要创建的,而是将原型对象分享给那些继承的对象。当然有时需要让每个继承对象独立拥有一份原型对象,此时我们就需要对原型对象进行复制。

由此我们可以看出,原型对象更适合在创建复杂对象时,对于那些需求一致在变化而导致我们对象结构不停改变,将那些比较稳定的属性和方法共用提取出来用继承的方法来实现。