目录

前言

创建型模式

单例模式

构造器+原型

简单工厂模式

工厂模式

创建型模式比较

结构性模式

模块模式

外观模式

混入模式

装饰模式

适配模式

行为型模式

观察者模式

中介者模式

命令模式

责任链模式

设计原则

单一职责原则

最少知识原则

开放-封闭原则

  • 前言

  为什么要学习设计模式?

  解耦合、解决复用、提高观察高度

  • 创建型模式

  1、单例模式

  场景:点击页面一个按钮,弹出遮罩层。

  遇到这个问题,首先我们想到用一个方法创建一个div,然后append到body上,实现点击事件,代码如下:

 function createTip(){
var div = document.createElement("div");
return document.body.appendChild(div);
}
$('#user').click( function(){
var tip = createTip();
$(tip).show();
});

  虽然功能实现了,但是这种方法有一个大问题:反复创建Dom!

  所以我们需要对它进行改造,改造完成之后,代码如下:

 var tipEl = document.createElement("div");
tipEl.style.display = "none";
document.body.appendChild(tipEl);
function showTip(){
tipEl.style.display = "";
}
$('#user').click( function(){
showTip();
});

  虽然避免了上面方法重复创建dom的缺点,但是这么实现还是有很大的弊端:1、暴露全局变量tipEl,尤其是在做组件化开发的时候要尽可能不要暴露全局变量,否则容易引起全局变量污染和冲突;2、创建完成之后用户可能不去点击,导致dom浪费。

  再一次升级,代码如下:

 var createTip = function(){
var tip;
return function(){
return tip ||
(tip=document.body.appendChild(document.createElement("div")))
}
}();
$('#user').click( function(){
var tip = createTip();
$(tip).show();
});

  好,这就是最终的单例模式实现了。

  为什么createTip要return一个function,而不是直接把tip||(tip=document.body.appendChild(document.createElement("div"))) 返回出来呢?

  因为这样可以做到惰性加载,只在需要的时候创建(这也是在click方法中var tip = createTip();要加上()的原因),否则浏览器遇到createTip的定义的时候就直接执行返回tip,这不是我们所希望的。

  总结:

  a、唯一实例:节约系统资源,提高性能;

  b、命名空间:减少全局变量的数量;

  c、实例控制:私有实例,提供访问接口

  d、适用场景:一个类只有一个实例,提供全局访问接口;资源共享的情况下,避免性能或其他损耗,如计数器、配置;资源控制的情况下,方便资源之间的互相通信,如线程池。

  2、构造器+原型

  场景:酒店有多种类型的房间,如何描述各种房间之间的关系。

  想到第一种,函数方法,代码如下:

 function Room(roomNumber, type, money) {
this.roomNumber = roomNumber;
this.type = type; this.money = money;
this.getInfo = function() {
return this.roomNumber + ', ' + this.type + ', ' + this.money;
};
}
var roomA = Room('101', '标间', 200);
console.log(roomA);//如何获取房间信息呢???

  如何获取房间信息成为一大问题,像第8行代码那样直接调用Room方法,其实this指向的是window对象,故Room方法内部的属性和方法赋值都是赋值给了window对象,所以即使可以通过window.getInfo()来调用,但是显得毫无意义。

  于是,我们想到了构造器的实现方法,代码如下:

 function Room(roomNumber, type, money) {
this.roomNumber = roomNumber;
this.type = type;
this.money = money;
this.getInfo = function() {
return this.roomNumber + ', ' + this.type + ', ' + this.money;
};
}
var roomA = new Room('101', '标间', 200);
console.log(roomA.getInfo());//101,标间,200
var roomB = new Room('102', '海景房', 799);
console.log(roomB.getInfo());//102,海景房,799

  每次调用使用new关键字实例化对象,然后调用getInfo方法。

  够简单、够直观,但是又有新问题,getInfo每次实例化都要重新创建,消耗内存。

  开启外部添加原型模式,代码如下:

 function Room(roomNumber, type, money) {
this.roomNumber = roomNumber;
this.type = type;
this.money = money;
}
Room.prototype.getInfo = function() {
return this.roomNumber + ', ' + this.type + ', ' + this.money;
}
var roomA = new Room('101', '标间',200);
console.log(roomA.getInfo());//101,标间,200
Room.prototype.book = function() {
console.log("book success");
}
roomA.book();//book success

  优点:方法共享,节约内存;随时添加方法且可以被实例化使用;

  缺点:方法零散,不利于管理。

  那就把原型统一起来,代码如下:

 function Room(roomNumber, type, money) {
this.roomNumber = roomNumber;
this.type = type;
this.money = money;
}
Room.prototype = {
constructor : Room,//这行代码很重要,因为已经重新定义了prototype的“指针”,如果不自己指定的话默认就是object,而不是Room
getInfo : function() {
return this.roomNumber + ', ' + this.type + ', ' +this.money
}
}
var roomA = new Room('101', '标间', 200);
console.log(roomA.getInfo());//101,标间,200
console.log(roomA.constructor == Room);//true
console.log(roomA instanceof Room);//true

  其中,所有的方法和变量都会暴露出来,这显然不够合理,更加优化的方式是把Room.prototype设计成揭示型的原型,利用自执行函数可以有效地保护私有变量和方法,只暴露自己想暴露的东西,代码如下:

 Room.prototype = function(){
var getInfo = function(){
return this.roomNumber + ', ' + this.type + ', ' + this.money;
}
return {
constructor:Room,
getInfo : getInfo
}
}()

  总结:

  a、适用场景:一个类有多个实例,且相互独立,如UI组件。

  3、简单工厂模式

  场景:某旅游网站有机票预订和酒店预订功能,如何实现它们的关系。

  场景实现,代码如下:

 var PRODUCT_TYPE = {
AIR_TICKET : "01",
HOTEL : "02"
}
function User(){
this.shopCart = [];
}
User.prototype = {
constructor : User,
order : function(productType){
var product = null;
switch(productType){
case PRODUCT_TYPE.AIR_TICKET:
product = new Flight();
case PRODUCT_TYPE.HOTEL:
product = new Hotel();
default:
}
this.shopCart.push(product);
}
}

  此时如果想增加火车票业务,删除机票业务,该怎么实现?需要改动的代码一点都不灵活,这时就需要我们理清用户和具体业务之间的关系,用户仅仅只和订单有关系。

  使用简单工厂模式,代码如下:

 var productFactory = (function(){
var productFoctories = {
"airTicket" : function(){
return new AirTicket();
},
"hotel" : function(){
return new Hotel();
}
}
return {
create : function(productType){
return productFoctories[productType];
}
}
})();
User.prototype = {
constructor : User,
order : function(productType){
var product = productFactory.create(productType);
this.shopCart.push(product);
}
}

  总结:

  对象的创建和使用分离,使用一个类生成实例。

  a、使用类User:使用类仅使用产品,职责单一;

  b、工厂类Factory:对象类的集中管理;

  c、对象类Hotel:易于扩展,仅影响工厂类;

  d、适用场景:根据不同参数产生不同实例,这些实例有些共性的场景,使用者只需使用产品,无需关注产品的创建细节。

  

  4、工厂模式

  场景:某旅游网站有很多种酒店,不同的酒店内也有很多种房间,如何清楚地描述它们之间的关系。

  工厂实现,代码如下:

 /*********RoomBase ***********/
var RoomBase = function(){};
RoomBase.prototype = {
constructor : RoomBase,
create : function(){
throw new Error("room create not impl!");
},
book : function(){
this.state = "1";
},
hasWindow : function(){
return true;
}
}
/********************/
var RoomA = function(){};
extend(RoomA, RoomBase);//继承
RoomA.prototype.create = function(type){
console.log("这是标间");
}
RoomA.prototype.hasWindow = function(){
return false;
}
var RoomB = function(){};
extend(RoomB, RoomBase);
RoomB.prototype.create = function(type){
console.log("这是大床房");
}
/*********HotelBase ***********/
var HotelBase = function(){
this.roomFoctory = {};
};
HotelBase.prototype = {
constructor : HotelBase,
getRoom : function(type){
return this.roomFactory[type];
},
bookRoom : function(type){
var room = this.getRoom(type);
room.book();//预订
}
}
var HotelA = function(){
this.roomFoctory = {
"RoomA" : function(){
return new RoomA();
},
"RoomB" : function(){
return new RoomB();
}
}
};
extend(HotelA, HotelBase);
var HotelB = function(){
this.roomFoctory = {
"RoomA" : function(){
return new RoomA();
},
"RoomC" : function(){
return new RoomC();
}
}
};
extend(HotelA, HotelBase);

  针对该旅游网站,网站管理酒店,酒店管理房间,网站不能直接管理房间,所有房间预订的功能全部走酒店的接口,跟网站没有关系,条理才比较清晰。

  总结:

  a、对象的创建和使用分离,子类决定成员变量的具体类;

  b、使用类HotelBase:提供对象类的管理;

  c、创建类HotelA:管理对象的实例化,多态;

  d、对象类Room:职责单一,扩展仅影响相关创建类;

  e、根据不同场景产生不同实例,这些实例有些共性的场景,使用者需要明确产品使用的场景,无需关心产品创建细节。

  5、创建型模式比较

  工厂模式:

    ①可创建多个相似对象;

    ②屏蔽了子对象类型(如上例中,使用Hotel预订的时候我们不知道它是A酒店还是B酒店,预订的时候是调用了HotelBase的基类中的book实现,根本没有关心它是酒店A还是酒店B)。

  构造函数模式:

    ①可创建多个相似对象;

    ②可识别子对象类型;

    ③创建多个完成相同任务的实例。

  原型模式:

    ①可创建多个相似对象;

    ②可识别子对象类型;

    ③多个实例共享公共方法。

  

  • 结构型模式

  1、模块模式

 var productFactory = (function(){
var productFoctories = {
"airTicket" : function(){
return new AirTicket();
},
"hotel" : function(){
return new Hotel();
}
}
return {
create : function(productType){
return productFoctories[productType];
}
}
})();

  总结:

  a、模块模式就是我们常说的命名空间,里面的东西根本不重要;

  b、保护私有接口,开放公共接口;

  c、模块化是面向对象的第一步;

  d、遵循单一职责原则。

  2、外观模式

 /****例1*****/
var product;
function getProduct(){
if(!product){
product = productFactory.create();
}
return product;
}
/****例2*****/
/** * 同时阻止事件默认行为和冒泡 * @param e */
var stopEvent = function(e){
e.stopPropagation();
e.preventDefault();
}

  对相关的操作进行一层包装,比如例2中需要我们每次阻止事件冒泡的时候打一个日志console.log('日志'),那我们只需要在stopEvent的方法中写就行了,而不需要在每个需要阻止事件冒泡的地方去打日志。

  总结:

  a、更高层次的接口抽象,隐藏底层的真实复杂性;

  b、遵循最少知识原则。(你只需要知道有一个接口可以实现你的需求,而你不需要知道接口中到底调用了哪些方法去实现你的需求)

  3、混入模式

  场景:一个房子可以用来出租或出售,在不同的场景下有不同的功能,比如出售还涉及到房产证等等其他的一系列东西,所以单单通过一个属性是办不到的,这个时候就需要使用混入模式。代码如下:

 /******基础对象********/
var Room = function(){};
Room.prototype = {
constructor:Room,
create:function(){
console.log('create');
},
book:function(){
console.log('book');
}
};
/******混入对象********/
var Goods = function(){};
Goods.prototype = {
sell:function(){
console.log('sell');
}
};
/******混入方法********/
var mixin = function (receiver, mixinObj) {
// 如果存在三个以上的参数的话,从第三个开始表示要混入的方法名
if (arguments[2]) {
for (var i = 2, len = arguments.length; i < len; i++) {
receiver.prototype[arguments[i]] = mixinObj.prototype[arguments[i]];
}
}
// 否则混入全部方法
else{
for(var methodName in mixinObj.prototype){
// 防止混入覆盖
if(!receiver.prototype[methodName]){
receiver.prototype[methodName] = mixinObj.prototype[methodName];
}
}
}
} /******混入操作********/
mixin(Room,Goods);
var roomA = new Room();
roomA.sell();//调用混入后的方法

  “房屋类”混入“商品类”的出售属性,房屋就具有了出售的功能,同理,如果混入了出租的属性,就具备了出租的功能。

  总结:

  a、通过函数复用达到对象扩充的目的;

  b、混入和继承的区别:混入只完成了方法的转移,两个对象还是两个对象,毫无任何关系;

  c、混入和组合的区别:组合是拥有的关系,比如酒店拥有房间,酒店可以操作房间,但是房间的属性还是房间的,不属于酒店。

 

  4、装饰模式

  场景:一个酒店的房间有淡季价格和旺季价格之分,如何实现。

 var Room = function(price){
this.price = price;
};
Room.prototype = {
constructor:Room,
getPrice:function(){
return this.price;
}
};
var LowSeasonRoom = function(room){
this.room = room;//注入Room,Room和LowSeasonRoom是组合的关系
};
LowSeasonRoom.prototype.getPrice = function(){
return this.room.getPrice()*0.9;
};
var HighSeasonRoom = function(room){
this.room = room;
};
HighSeasonRoom.prototype.getPrice = function(){
return this.room.getPrice()*1.2;
};
/****************使用*****************/
var roomA = new LowSeasonRoom(new Room(200));
console.log(roomA.getPrice());//

  总结:

  a、动态改变对象实现对象扩充;(比如密码输入框是文本输入框的装饰模式,从文本输入框中单独剥离出来单独实现密码输入框的业务需求)

  b、缺点是增加了架构的复杂度。

  5、适配模式

  总结:

  a、主要用来解决对象之间的不兼容性;

  b、sum函数:可以不只是两个数的sum,可以通过arguments适配多个参数的sum;

  c、jQuery版本升级:比如版本升级之后废弃了某个方法一定是有其他的方法来兼容;

  d、apply函数:指定作用域随时都可以调用。

  • 行为型模式

  1、观察者模式

  场景:酒店的某个房间需要客房服务,酒店如何响应。

 var Room = function(){};
Room.prototype = {
constructor : Room,
service : function(){
this.fire("service", this.roomId);
}
}
var Hotel = function(){};
Hotel.prototype.addRoom = function(room){
room.on("service", function(){
//TODO 服务
});
}

  总结:

  a、命令的发起者和执行者解耦合,don't call us,we will call you;

  b、一个发起者可能对应多个执行者;

  c、一个执行者也可能是其他执行者的命令发起者

  d、常用的场景:比如我们页面中经常会用到iframe,一个子页面需要调用父页面的方法,经常会使用parent.functionNmae()去调用,但是一旦取不到父页面,这里就会报错,所以正确的处理方式是在父页面监听事件,在子页面触发事件,这样就算服务不到也不会报错,通常在父子页面中都存在的是body,固我们习惯性把事件的监听和触发放到body上执行。

  2、中介者模式

  场景:一个酒店某个房间需要服务,酒店中有多个服务员,这时候该怎么实现呢。

  这个时候就不能用观察者模式,因为观察者模式是不确定谁来服务的,万一酒店中所有的服务员都来服务怎么办,得由酒店来确定到底哪个服务员过来提供服务。

 var Room = function(){};
Room.prototype = {
constructor : Room,
service : function(){
this.fire("service", this.roomId, type);
}
}
var Waiter = function(){};
Waiter.prototype = {
constructor : Waiter,
service : function(){
//TODO 服务
}
}
var Hotel = function(){};
Hotel.prototype.addRoom = function(room){
room.on("service", function(roomId, type){
if(type == "设备修理"){
waiterA.service();
}else{
if(roomId.startWith("3")){
waiterB.service();
}else{
waiterC.service();
}
}
});
}

  总结:

  a、管理复杂对象关系:把多对多拆分成多对一;

  b、对象间的耦合转移至中介者;

  c、中介者的稳定性至关重要。

  3、命令模式

  场景:如果酒店提供的服务有多种多样,该如何实现。

  此时就需要用到命令模式,代码如下:

 var Services = {
clean : function(){
console.log("打扫卫生");
},
consult : function(){
console.log("咨询");
}
}
var ServcieCommand = function(){
return {
execute : function(receiver, command){
receiver[command]();
}
}
}();

  总结:

  a、命令的发起和实现解耦合

  4、责任链模式

  场景:假设某个酒店发生了斗殴,如果酒店内部可以处理则自己处理,否则就让警察处理,责任一层层转移。

 var Hotel = function(){
this.on("fight", function(){
if(this.canDo()){//内部处理
this.do();
}else{//报警
Police.do();
}
});
};
Police = function(){
return {
do : function(){
if(this.canDo()){
console.log("over");
}else{
//TODO 转上级处理。。。
}
}
}
}();

  总结:

  a、每个节点只知道下线,不知道最终执行者;

  b、可能出现无人处理的情况;

  c、缺点调试困难(所以需要我们在每个执行环节打日志)。

  • 设计原则

  1、单一职责原则

  如何确定一个职责or两个职责:

  a、职责被定义为“引起变化的原因”;

  b、若两个职责总是同时变化,则可以作为一个职责;(比如请求数据之后渲染图表)

  c、发生变化才对职责的限定有意义,不变没有必要分离

  违反不一定是坏事:

  比如:$.attr()这个方法有赋值也有取值的职责,但是能很好地找到平衡点。

  2、最少知识原则

  减少对象之间的联系:

  a、两个对象之间不必直接通信则不直接通信,通过第三方实现通信;

  b、如中介者模式和外观模式

  违反不一定是坏事:

  比如:$.ajax 和 $.post

  3、开放-封闭原则

  开放和封闭(对扩展开放,对修改关闭):

  a、当需要改变时,可以使用增加代码的方式,但不允许改动程序的源代码,比如组件有bug或不满足需求时;

  b、比如一个很老系统新的需求window.onload的时候需要统一打一下日志,如何处理?

  法1:使用jQuery,因为jQuery的事件可以监听多个;

  法2:重写onload方法,重写的时候先调它的方法,再调自己的方法。

  找出变化的地方很重要:

  a、挑选出最容易发生变化的地方进行抽象,来封闭这些变化;

  b、不可避免发生修改时,尽量修改容易修改的地方,如修改配置比修改源码简单。

  

  写在最后:本文为参加公司前端大牛分享整理所得,转载请注明出处或取得作者同意。

举个栗子学习JavaScript设计模式的更多相关文章

  1. JavaScript的学习--JavaScript设计模式的总结

    这篇博客只是自己对设计模式的理解的备忘~ 看完了<JavaScript设计模式>这本书,一直没有写博客记录一下,最近抽出时间来重读了一下,就顺便记录一下~ 如果你只是想粗略了解一下Java ...

  2. 为什么学习JavaScript设计模式,因为它是核心

    那么什么是设计模式呢?当我们在玩游戏的时候,我们会去追求如何最快地通过,去追求获得已什么高效率的操作获得最好的奖品:下班回家,我们打开手机app查询最便捷的路线去坐车:叫外卖时候,也会找附近最近又实惠 ...

  3. 学习javascript设计模式之中介者模式

    1.中介者模式的作用就是解除对象与对象之间的紧耦合关系.增加一个中介者对象后,所有的相关对象都通过中介者来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介对象即可.中介者使各对象之间耦合 ...

  4. 学习javascript设计模式之代理模式

    1.代理模式为一个对象提供一个代用品或占位符,以便控制对它的访问. 2.不用代理模式: 客户 -> 本体  使用代理模式:  客户 -> 代理 -> 本体 3.例子场景1 点击操作与 ...

  5. 学习javascript设计模式之发布-订阅(观察者)模式

    1.发布-订阅模式又叫观察者模式,它定义对象之间一种一对多的依赖关系. 2.如何实现发布-订阅模式 2-1.首先指定好发布者 2-2.给发布者添加一个缓冲列表,用户存放回调函数以便通知订阅者 2-3. ...

  6. 学习javascript设计模式之单例模式

    1.单例模式的核心是确保只有一个实例,并提供全局访问. 2.惰性单例 指的是在需要的时候才创建对象实例. 如在页面中创建唯一div 普通做法 var createDiv = (function(){  ...

  7. 学习javascript设计模式之装饰者模式

    1.装饰者模式定义:给对象动态添加职责的方式称为装饰者(decorator)模式. js如何实现装饰者模式 通过保存原函数引用方式改写某函数 window.onload = function(){al ...

  8. Javascript设计模式学习一

    学习Javascript设计模式之前,需要先了解一些相关知识,面向对象的基础知识.this等重要概念,以及掌握一些函数式编程的技巧. Js多态 多态的思想:实际上是把“做什么”和“谁去做”分离开来.例 ...

  9. 探索Javascript设计模式---单例模式

    最近打算系统的学习javascript设计模式,以便自己在开发中遇到问题可以按照设计模式提供的思路进行封装,这样可以提高开发效率并且可以预先规避很多未知的问题. 先从最基本的单例模式开始. 什么是单例 ...

随机推荐

  1. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

  2. 聊聊Unity项目管理的那些事:Git-flow和Unity

    0x00 前言 目前所在的团队实行敏捷开发已经有了一段时间了.敏捷开发中重要的一个话题便是如何对项目进行恰当的版本管理.项目从最初使用svn到之后的Git One Track策略再到现在的GitFlo ...

  3. JAVA回调机制(CallBack)详解

    序言 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回 ...

  4. 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密

    下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...

  5. 分治法求解最近对问题(c++)

    #include"stdafx.h" #include<iostream> #include<cmath> #define TRUE 1 #define F ...

  6. HTML文档头部

    前面的话   在声明文档类型之后,HTML文档的下一部分为<html>标签,告知浏览器应将括在<html>...</html>内的所有内容解析为HTML.然后是HT ...

  7. Spring中Bean的作用域、生命周期

                                   Bean的作用域.生命周期 Bean的作用域 Spring 3中为Bean定义了5中作用域,分别为singleton(单例).protot ...

  8. arcgis api for js入门开发系列四地图查询(含源代码)

    备注:由于实现本篇功能的需求,修改了地图数据的dlsearch.mxd,然后更新了地图服务,需要的在文章最后有提供最新的mxd以及源代码下载的 上一篇实现了demo的地图工具栏,本篇新增地图查询功能, ...

  9. atitit.attilax的软件 架构 理念.docx

    atitit.attilax的软件 架构 理念.docx 1. 预先规划.1 2. 全体系化1 3. 跨平台2 4. 跨语言2 5. Dsl化2 5.1. 界面ui h5化2 6. 跨架构化2 7. ...

  10. Ajax.BeginForm方法 参数

    感谢博主 http://www.cnblogs.com/zzgblog/p/5454019.html toyoung 在Asp.Net的MVC中的语法,在Razor页面中使用,替代JQuery的Aja ...