学习极客学院课程JavaScript设计模式 所做的笔记,参考了汤姆大叔 的深入理解JavaScript系列文章 。主要内容是各种设计模式的概念解读,作用和注意事项,现阶段对各种设计模式的理解并不是很深,实际运用到了之后再来补充。
设计原则
对扩展开放,对修改关闭
子类继承父类,单独掉完全可以运行
引用一个对象,如果这个对象有底层类型,直接引用底层类型
每一个接口应该是一个角色
新的对象应使用一些一有对象,使之成为新对象的一部分
一个对象应对其他对象有尽可能少的了解
单例模式 保证一个类只有一个实例,实现的方法一般是先判断实例是否存在,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
模式作用:
模块间的通信
系统中某个类的对象只能存在一个
保护自己的属性和方法
注意事项:
注意this的使用
闭包容易造成内存泄漏,不需要的赶快干掉
注意new的成本
var mySingleton = {
property1 : "something" ,
property2 : "something else" ,
method1 : function ( ) {
console .log('hello world' );
}
};
构造函数模式 构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。
模式作用:
用于创建特定类型的对象
第一次声明的时候给对象赋值
自己声明构造函数,赋予属性和方法
注意事项:
声明函数的时候处理业务逻辑
区分和单例的区别,配合单例实现初始化
构造函数大写字母开头
注意new的成本(继承)
function Car (model, year, miles ) {
this .model = model;
this .year = year;
this .miles = miles;
this .output= function ( ) {
return this .model + "走了" + this .miles + "公里" ;
};
}
var tom= new Car("大叔" , 2009 , 20000 );
var dudu= new Car("Dudu" , 2010 , 5000 );
建造者模式 建造者模式可以将一个复杂对象的构建与其表现相分离,使得同样的构建过程可以创建不同的表示。也就是说如果我们用了建造者模式,那么用户就需要指定需要建造的类型就可以得到他们,而具体建造的过程和细节就不需要知道了。建造者模式主要用于“分步骤构建一个复杂的对象”。
模式作用:
分步创建一个复杂的对象
解耦封装过程和具体创建的组件
无需关心组件如何组装
注意事项:
一定要一个稳定的算法进行支持
加工工艺是暴露的
function getBeerById (id, callback ) {
asyncRequest('GET' , 'beer.uri?id=' + id, function (resp ) {
callback(resp.responseText);
});
}
var el = document .querySelector('#test' );
el.addEventListener('click' , getBeerByIdBridge, false );
function getBeerByIdBridge (e ) {
getBeerById(this .id, function (beer ) {
console .log('Requested Beer: ' + beer);
});
}
工厂模式 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类,而子类可以重写接口方法以便创建的时候指定自己的对象类型。
模式作用:
对象的构建十分复杂
需要依赖具体的环境创建不同实例
处理大量具有相同属性的小对象
注意事项:
不能滥用工厂模式,有时候仅仅是给代码增加复杂度
var page = page || {};
page.dom = page.dom || {};
page.dom.Text = function ( ) {
this .insert = function (where ) {
var txt = document .createTextNode(this .url);
where.appendChild(txt);
};
};
page.dom.Link = function ( ) {
this .insert = function (where ) {
var link = document .createElement('a' );
link.href = this .url;
link.appendChild(document .createTextNode(this .url));
where.appendChild(link);
};
};
page.dom.Image = function ( ) {
this .insert = function (where ) {
var im = document .createElement('img' );
im.src = this .url;
where.appendChild(im);
};
};
page.dom.factory = function (type ) {
return new page.dom[type];
}
var o = page.dom.factory('Link' );
o.url = 'http://www.cnblogs.com' ;
o.insert(document .body);
外观模式 外观模式Facade
为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用。
模式作用:
在设计初期,应该有意识地将不同的两个层分离,比如经典的三层结构
在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,增加外观Facade可以提供一个简单的接口,减少他们之间的依赖
在维护一个遗留的大型系统时,可能这个系统已经很难维护了,这时候使用外观Facade也是非常合适的,为系统开发一个外观Facade类,为设计粗糙和高度复杂的遗留代码提供比较清晰的接口,让新系统和Facade对象交互,Facade与遗留代码交互所有的复杂工作。
注意事项:
外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性
var addMyEvent = function (el, ev, fn ) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false );
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn);
} else {
el['on' + ev] = fn;
}
};
var mobileEvent = {
stop: function (e ) {
e.preventDefault();
e.stopPropagation();
}
};
代理模式 代理模式Proxy
,为其他对象提供一种代理以控制对这个对象的访问。代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。
模式作用:
远程代理(一个对象将不同空间的对象进行局部代理)
虚拟代理(根据需要创建开销很大的对象如渲染网页暂时用占位代替真图)
安全代理(控制真实对象的访问权限)
智能指引(调用对象代理处理另外一些事情如垃圾回收机制)
注意事项:
不能滥用代理,有时候仅仅是给代码增加复杂度
var girl = function (name ) {
this .name = name;
};
var dudu = function (girl ) {
this .girl = girl;
this .sendGift = function (gift ) {
alert("Hi " + girl.name + ", dudu送你一个礼物:" + gift);
}
};
var proxyTom = function (girl ) {
this .girl = girl;
this .sendGift = function (gift ) {
(new dudu(girl)).sendGift(gift);
}
};
观察者模式 观察者模式又叫发布订阅模式Publish/Subscribe
,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
模式作用:
支持简单的广播通信,自动通知所有已经订阅过的对象
页面加载后目标对象很容易与观察者存在一种动态关联,增加了灵活性
目标对象与观察者之间的抽象耦合关系能够单独拓展以及重用
注意事项:
监听要在触发之前
function Observer ( ) {
this .fns = [];
}
Observer.prototype = {
subscribe : function (fn ) {
this .fns.push(fn);
},
unsubscribe : function (fn ) {
this .fns = this .fns.filter(
function (el ) {
if (el !== fn) {
return el;
}
}
);
},
update : function (o, thisObj ) {
var scope = thisObj || window ;
this .fns.forEach(
function (el ) {
el.call(scope, o);
}
);
}
};
var o = new Observer;
var f1 = function (data ) {
console .log('Robbin: ' + data + ', 赶紧干活了!' );
};
var f2 = function (data ) {
console .log('Randall: ' + data + ', 找他加点工资去!' );
};
o.subscribe(f1);
o.subscribe(f2);
o.update("Tom回来了!" )
o.unsubscribe(f1);
o.update("Tom回来了!" );
策略模式 策略模式定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化不会影响到使用算法的客户。
模式作用:
所有的这些算法都是做相同的事情,只是实现不同
以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合
单独定义算法类,也方便了单元测试
注意事项:
不仅可以封装算法,也可以用来封装几乎任何类型的规则,只要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑使用策略模式来处理各种变化
var validator = {
types: {},
messages: [],
config: {},
validate: function (data ) {
var i, msg, type, checker, result_ok;
this .messages = [];
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this .config[i];
checker = this .types[type];
if (!type) {
continue ;
}
if (!checker) {
throw {
name : "ValidationError" ,
message : "No handler to validate type " + type
};
}
result_ok = checker.validate(data[i]);
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this .messages.push(msg);
}
}
}
return this .hasErrors();
},
hasErrors: function ( ) {
return this .messages.length !== 0 ;
}
};
validator.types.isNonEmpty = {
validate : function (value ) {
return value !== "" ;
},
instructions : "传入的值不能为空"
};
validator.types.isNumber = {
validate : function (value ) {
return !isNaN (value);
},
instructions : "传入的值只能是合法的数字,例如:1, 3.14 or 2010"
};
validator.types.isAlphaNum = {
validate : function (value ) {
return !/[^a-z0-9]/i .test(value);
},
instructions : "传入的值只能保护字母和数字,不能包含特殊字符"
};
var data = {
first_name : "Tom" ,
last_name : "Xu" ,
age : "unknown" ,
username : "TomXu"
};
validator.config = {
first_name : 'isNonEmpty' ,
age : 'isNumber' ,
username : 'isAlphaNum'
};
validator.validate(data);
if (validator.hasErrors()) {
console .log(validator.messages.join("\n" ));
}
命令模式 用于将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及执行可撤销的操作。也就是说该模式旨在将函数的调用、请求和操作封装成一个单一的对象,然后对这个对象进行一系列的处理。此外,可以通过调用实现具体函数的对象来解耦命令对象与接收对象。
模式作用:
将函数的封装、请求、调用结合为一体
调用具体的函数解耦命令对象与接受对象
提高程序模块化的灵活性
注意事项:
不需要接口一致,直接调用函数即可,以免造成浪费
$(function ( ) {
var CarManager = {
requestInfo: function (model, id ) {
return 'The information for ' + model +
' with ID ' + id + ' is foobar' ;
},
buyVehicle: function (model, id ) {
return 'You have successfully purchased Item '
+ id + ', a ' + model;
},
arrangeViewing: function (model, id ) {
return 'You have successfully booked a viewing of '
+ model + ' ( ' + id + ' ) ' ;
}
};
})();
CarManager.execute({ commandType : "buyVehicle" , operand1 : 'Ford Escort' , operand2 : '453543' });
CarManager.execute = function (command ) {
return CarManager[command.request](command.model, command.carID);
};
CarManager.execute({ request : "arrangeViewing" , model : 'Ferrari' , carID : '145523' });
CarManager.execute({ request : "requestInfo" , model : 'Ford Mondeo' , carID : '543434' });
CarManager.execute({ request : "requestInfo" , model : 'Ford Escort' , carID : '543434' });
CarManager.execute({ request : "buyVehicle" , model : 'Ford Escort' , carID : '543434' });
迭代器模式 提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
模式作用:
为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作
对于集合内部结果常常变化各异,我们不想暴露其内部结构的话,但又响让客户代码透明底访问其中的元素,这种情况下我们可以使用迭代器模式
注意事项:
一般的迭代,我们至少要有两个方法,hasNext()
和Next()
,这样才能做到遍历所有对象
遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach
里不允许修改item
)
var agg = (function ( ) {
var index = 0 ,
data = [1 , 2 , 3 , 4 , 5 ],
length = data.length;
return {
next : function ( ) {
var element;
if (!this .hasNext()) {
return null ;
}
element = data[index];
index = index + 2 ;
return element;
},
hasNext : function ( ) {
return index < length;
},
rewind : function ( ) {
index = 0 ;
},
current : function ( ) {
return data[index];
}
};
} ());
while (agg.hasNext()) {
console .log(agg.next());
}
职责链模式 职责链模式Chain of responsibility
是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
也就是说,请求以后,从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确知道哪一个对象将会处理它——也就是该请求有一个隐式的接受者implicit receiver
。根据运行时刻,任一候选者都可以响应相应的请求,候选者的数目是任意的,你可以在运行时刻决定哪些候选者参与到链中。
模式作用:
dom的冒泡有些类似职责链
nodejs当controller中有很多负责操作逻辑的时候拆分中间件
解耦发送者和接收者
注意事项:
javascript中的每一次.操作是有代价的,要在必要的时候应用
适配器模式 适配器模式Adapter
是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。
模式作用:
使用一个已经存在的对象,但其方法或接口不符合你的要求
创建一个可复用的对象,该对象可以与其他不相关或不可见的对象协同工作
使用已经存在的一个或多个对象,但是不能进行继承已匹配它的接口
注意事项:
与代理模式的区别,代理模式在不改变它的接口的条件下,为另外一个对象定义了一个代理
var Duck = function ( ) {
};
Duck.prototype.fly = function ( ) {
throw new Error ("该方法必须被重写!" );
};
Duck.prototype.quack = function ( ) {
throw new Error ("该方法必须被重写!" );
}
var Turkey = function ( ) {
};
Turkey.prototype.fly = function ( ) {
throw new Error (" 该方法必须被重写 !" );
};
Turkey.prototype.gobble = function ( ) {
throw new Error (" 该方法必须被重写 !" );
};
var MallardDuck = function ( ) {
Duck.apply(this );
};
MallardDuck.prototype = new Duck();
MallardDuck.prototype.fly = function ( ) {
console .log("可以飞翔很长的距离!" );
};
MallardDuck.prototype.quack = function ( ) {
console .log("嘎嘎!嘎嘎!" );
};
var WildTurkey = function ( ) {
Turkey.apply(this );
};
WildTurkey.prototype = new Turkey();
WildTurkey.prototype.fly = function ( ) {
console .log("飞翔的距离貌似有点短!" );
};
WildTurkey.prototype.gobble = function ( ) {
console .log("咯咯!咯咯!" );
};
var TurkeyAdapter = function (oTurkey ) {
Duck.apply(this );
this .oTurkey = oTurkey;
};
TurkeyAdapter.prototype = new Duck();
TurkeyAdapter.prototype.quack = function ( ) {
this .oTurkey.gobble();
};
TurkeyAdapter.prototype.fly = function ( ) {
var nFly = 0 ;
var nLenFly = 5 ;
for (; nFly < nLenFly;){
this .oTurkey.fly();
nFly = nFly + 1 ;
}
};
var oMallardDuck = new MallardDuck();
var oWildTurkey = new WildTurkey();
var oTurkeyAdapter = new TurkeyAdapter(oWildTurkey);
oMallardDuck.fly();
oMallardDuck.quack();
oWildTurkey.fly();
oWildTurkey.gobble();
oTurkeyAdapter.fly();
oTurkeyAdapter.quack();
模板方法 模板方法TemplateMethod
定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法是一种代码复用的基本技术,在类库中尤为重要,因为他们提取了类库中的公共行为。模板方法导致一种反向的控制结构,这种结构就是传说中的“好莱坞法则”,即“别找找我们,我们找你”,这指的是父类调用一个类的操作,而不是相反。具体体现是面向对象编程编程语言里的抽象类(以及其中的抽象方法),以及继承该抽象类(和抽象方法)的子类。
模式作用:
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
各子类中公共的行为应被提取出来并集中到一个公共父类中的避免代码重复,不同之处分离为新的操作,最后,用一个钓鱼这些新操作的模板方法来替换这些不同的代码
控制子类扩展,模板方法只在特定点调用“hook”操作,这样就允许在这些点进行扩展
注意事项:
和策略模式不同,模板方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法
var CaffeineBeverage = function ( ) {
};
CaffeineBeverage.prototype.prepareRecipe = function ( ) {
this .boilWater();
this .brew();
this .pourOnCup();
if (this .customerWantsCondiments()) {
this .addCondiments();
}
};
CaffeineBeverage.prototype.boilWater = function ( ) {
console .log("将水烧开!" );
};
CaffeineBeverage.prototype.pourOnCup = function ( ) {
console .log("将饮料到再杯子里!" );
};
CaffeineBeverage.prototype.brew = function ( ) {
throw new Error ("该方法必须重写!" );
};
CaffeineBeverage.prototype.addCondiments = function ( ) {
throw new Error ("该方法必须重写!" );
};
CaffeineBeverage.prototype.customerWantsCondiments = function ( ) {
return true ;
};
var Coffee = function ( ) {
CaffeineBeverage.apply(this );
};
Coffee.prototype = new CaffeineBeverage();
Coffee.prototype.brew = function ( ) {
console .log("从咖啡机想咖啡倒进去!" );
};
Coffee.prototype.addCondiments = function ( ) {
console .log("添加糖和牛奶" );
};
Coffee.prototype.customerWantsCondiments = function ( ) {
return confirm("你想添加糖和牛奶吗?" );
};
var Tea = function ( ) {
CaffeineBeverage.apply(this );
};
Tea.prototype = new CaffeineBeverage();
Tea.prototype.brew = function ( ) {
console .log("泡茶叶!" );
};
Tea.prototype.addCondiments = function ( ) {
console .log("添加柠檬!" );
};
Tea.prototype.customerWantsCondiments = function ( ) {
return confirm("你想添加柠檬嘛?" );
};
原型模式 原型模式prototype
是指用原型实例指向创建对象的种类,并且通过拷贝这些原型创建新的对象。
对于原型模式,我们可以利用JavaScript特有的原型继承特性去创建对象的方式,也就是创建的一个对象作为另外一个对象的prototype属性值。原型对象本身就是有效地利用了每个构造器创建的对象,例如,如果一个构造函数的原型包含了一个name属性,那通过这个构造函数创建的对象都会有这个属性。
模式作用:
原型对象本身就是有效地利用了每个构造器创建的对象
注意事项:
注意浅拷贝和深拷贝的问题,免得出现引用问题
在现有的文献里查看原型模式的定义,没有针对JavaScript的,你可能发现很多讲解的都是关于类的,但是现实情况是基于原型继承的JavaScript完全避免了类class
的概念。我们只是简单从现有的对象进行拷贝来创建对象。
var vehiclePrototype = {
init : function (carModel ) {
this .model = carModel;
},
getModel : function ( ) {
console .log('车辆模具是:' + this .model);
}
};
function vehicle (model ) {
function F ( ) { };
F.prototype = vehiclePrototype;
var f = new F();
f.init(model);
return f;
}
var car = vehicle('福特Escort' );
car.getModel();
为正常使用来必力评论功能请激活JavaScript