《JavaScript设计模式》----张荣铭(一)
首先说一下什么是设计模式?以及我们为什么要学习设计模式?
设计模式的定义是:设计模式是在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
也可以通俗的理解为:设计模式在某个特定场景下都某种问题的解决方案。当然,这也就是为什么我们要学习设计模式的原因。本书将设计模式按照类型分成六大类
- 创建型设计模式
- 结构型设计模式
- 行为型设计模式
- 技巧型设计模式
- 架构型设计模式
1 创建型设计模式
创造型设计模式是一类处理对象创建的设计模式,通过某种方式控制对象的创建来避免基本对象创建时可能导致设计上的问题。比如我们常见的简单工厂模式。
1.1简单工厂模式
简单工厂模式的理念就是创建对象,我们不需要对使用者暴露我们的创建逻辑,也不需要让使用者记住每个一个类的创建方法,只需要记住有工厂类可以使用,并且告诉我你要创建什么样的对象就可以。
var Basketball = function() {
this.intro = '篮球在中国很受欢迎'
}
Basketball.prototype = {
getMember: function() {
console.log('篮球需要5名运动员');
}
}
var Football = function() {
this.intro = '足球在中国很受欢迎'
}
Football.prototype = {
getMember: function() {
console.log('篮球需要11名运动员');
}
}
var Tennis = function() {
this.intro = '网球在中国很受欢迎'
}
Tennis.prototype = {
getMember: function() {
console.log('网球需要1名运动员');
}
}
var SportsFactory = function(type) {
switch(type) {
case 'CBA':
return new Basketball();
case 'WordCup':
return new Basketball();
case 'FrenchOpen':
return new Basketball();
}
}
var basketball = SportsFactory('CBA');
console.log(basketball);
console.log(basketball.intro);
basketball.getMember();
这就是一个简单的工厂模式,如果要创建的对象有很多地方是相同的,可以抽象提取出来公用。
1.2 工厂方法模式
使用简单工厂模式之后,会发现当我们每新增一种运动类型,都需要先创建类,然后在工厂中注册,相当于每天夹一个类就要修改两处。如果过于频繁的新增类型就显得简单工厂模式并不那么优雅。这时候可以试试工厂方法模式。
// 这里避免新手在创建类的时直接var f = Factory(),我们称之为安全模式
var Factory = function(type, content) {
if (this instanceof Factory) {
var s = new this[type](content);
return s;
} else {
return new Factory(type, content);
}
}
Factory.prototype = {
Java: function(content) {
// ...
},
JavaScript: function(content) {
// ...
}
C: function(content) {
// ...
}
PHP: function(content) {
// ...
}
}
var php = new Factory('php', 'php是世界上最好的语言');
这样以后我们在拓展,添加其他类时,就只需要在Factory这个工厂类的原型里面新增内容就可以了。
1.3 抽象工厂模式
抽象工厂模式,和其他工厂模式不同,不是用来创建具体对象的,只是显性的定义一些功能,并没有具体的实现。所以我们一般用它作为父类来创建一些子类,(是不是不太好理解?先看例子)
// 抽象工厂方法
var VehicleFactory = function(subType, superType) {
if (typeof VehicleFactory[subType] === 'function') {
funtion F(){};
F.porototype = new VehicleFactory[superType]();
subType.constructor = subType;
subType.protoType = new F();
} else {
throw new Error('未创建该抽象类');
}
}
// 小汽车抽象类
VehicleFactory.Car = function() {
this.type = 'Car';
}
VehicleFactory.Car.prototype = {
getPrice: function() {
return new Error('抽象方法不能调用')
},
getSpeed: function() {
return new Error('抽象方法不能调用')
}
}
// 公交车抽象类
VehicleFactory.Bus = function() {
this.type = 'Bus';
}
VehicleFactory.Bus.prototype = {
getPrice: function() {
return new Error('抽象方法不能调用')
},
getPassengerNum: function() {
return new Error('抽象方法不能调用')
}
}
// 通过抽象工厂创建宝马汽车类
var BMW = function(price, speed) {
this.price = price;
this.speed = speed;
}
VehicleFactory(BMW, 'Car');
BMW.prototype.getPrice = function() {
return this.price;
}
BMW.prototype.getSpeed = function() {
return this.speed;
}
// 通过抽象工厂创建兰博基尼汽车类
var Lamborghini = function(price, speed) {
this.price = price;
this.speed = speed;
}
VehicleFactory(Lamborghini, 'Car');
Lamborghini.prototype.getPrice = function() {
return this.price;
}
Lamborghini.prototype.getSpeed = function() {
return this.speed;
}
// ...
var bmw525 = new BMW(80, 100);
console.log(bmw525.getPrice());
console.log(bmw525.type);
在上述代码中,我们首先声明了一个抽象工厂,里面可以生产Car,Bus等类型的汽车。然后我们创建具体的类,比如BMW是属于汽车类的,那么就通过VehicleFactory(BMW, ‘Car’)等操作对父类抽象的属性就行完善,BMW就是一个可以使用的类。通过BMW这个类,我们就可以创建BMW525,BMW740等你梦寐以求的车子了。
1.4 建造者模式
和工厂模式来对比,工厂模式主要是为了创建对象实例。而创建者模式虽然说也是创建实例,而它更多关心的是创建这个对象的整个过程。顾名思义,建造者嘛。
function Human = function(name) {
this.name = name;
}
Human.prototype = {
getName: function() {
return this.name;
}
}
function Work = function(work) {
this.work = work;
}
Work.prototype = {
getWork: function() {
return this.work;
}
}
// ...
var Person = function(name, work) {
var _person = new Human(name);
person.work = new Work(work);
// ... 等等一系列属性
}
var person1 = new Person('zaoren', 'fe');
1.5 单例(Singleton)模式
单例模式,限制了类的实例化次数只能有一次。简单的说就是在单例模式下,当创建某个类的实例时会去判断,该类是否已经声明了实例,如果已经声明,则返回该实例。如果还没有声明,则创建并返回(比如服务端的Session)。
function createMessage(name) {
this.name = name;
this.getName = () => {
alert(this.name);
};
}
// 我们这里使用代理模式创建单例,书上讲的是使用getInstance来获取实例
const proxyMode = (() => {
var instance = null;
return function(name) {
if(!instance) {
instance = new createMessage(name)
}
return instance;
}
})()
const msgA = new proxyMode('msgA');
const msgB = new proxyMode('msgB');
console.log(msgA === msgB);
其实单例模式比较重要的一个点是,使用了闭包,instance这个变量是一直没有被销毁的。
除此之外,单例还可以推迟创建,这通常是因为它们还需要一些信息,而这些信息在初始化的时候无法获得,等我们获得了初始化的信息之后,再去创建这个单例。
讲个场景:比如我们在使用弹出框的时候,这个弹出框可以理解成是一个单例,但是这个弹出框中显示的内容需要我点击列表页中的某一行后才能获取。那么是不是可以在我们点击之后再去创建这个弹出框呢?创建了以后,下一次点击只需要控制显隐。又比如,这个弹框我始终不需要去点击,那么是不是就不需要创建这个弹窗,假设是一个创建很耗费时的对象,是不是节省了很多资源。
const getSingle = function(fn) {
let instance = null;
return function() {
return instance || (instance = fn.apply(this, arguments))
}
}
const createModal = () => {
const div = document.createElement('div');
div.innerHtml = '随意一点'
// ...
return div;
}
const createSingleModalDiv = getSingle(createModal);
button.onClick = () => {
const div = createSingleModalDiv();
div.style.display = 'block';
}
思考: 目前我们Modal使用最多的方式就是通过 {flag && }来处理,这样子虽然方便,也不需要重新处理Modal重新render的数据,但是Modal会多次创建。
1.6 原型模式
由于使用构造函数模式创建出来的对象都有自己的属性和方法。并不是说这样不可以,但是当我们这些对象的某些属性和方法都是相同的,也许就会多占用内存空间,于是基于构造函数的原型模式就有了
原型模式的定义是这样的:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
简单的说,就是将一些简单而又差异化的属性放在构造函数中,将一些消耗资源比较大的方法放在基类的原型中。然后在子类中,再对需要重写的方法进行重写。来看个例子
// 假设有一个轮播组件基类,他需要 imageList,contaienr,createImage,changeImage属性
// 但是createImage 和 changeImage属性是公用的,所以放在原型方法里
var LoopImages = function(imageList, container) {
this.imageList = imageList;
this.container = container;
}
LoopImages.porototype = {
createImage: function() {
console.log('success!');
}
changeImage: function() {
console.log('success');
}
}
// 那么我们在基于基类创建子类时候,就只需要定义我们的imageList 和 container部分(当然在必要时可以改写createImage,changeImage方法)
// 假装 创建一个上下滑动的轮播组件
var SlideLoopImgs = function(imageList, container) {
LoopImages.call(this, imgList, container);
}
SlideLoopImgs.prototype = new LoopImages() // 是不是有点组合继承的味道了?
SlideLoopImgs.prototype.changeImage = function() {
console.log('这是一个上下滑动的轮播组件');
}
// 假装 创建一个渐隐切换的轮播组件
var FadeLoopImgs = function(imageList, container) {
LoopImages.call(this, imgList, container);
}
FadeLoopImgs.prototype = new LoopImages() // 是不是有点组合继承的味道了?
FadeLoopImgs.prototype.changeImage = function() {
console.log('这是一个渐隐切换的轮播组件');
}