阅读《JavaScript设计模式》(2009),电子版资源:
链接:https://pan.baidu.com/s/1smWE-Xcwsapn65jnlw8cvg
提取码:hkke
function startAnimation(){
...
}
function stopAnimation(){
...
}
var Anim=function(){
...
};
Anim.prototype.start=function(){
...
};
Anim.prototype.stop=function(){
...
};
var Anim=function(){
...
};
Anim.prototype={
start:function(){
...
},
stop=function(){
...
}
};
Function.prototype.method=function(name,fn){
this.prototype[name]=fn;
};
var Anim=function(){
...
};
Anim.method('start',function(){
...
});
Anim.method('stop',function(){
...
});
Function.prototype.method=function(name,fn){
this.prototype[name]=fn;
return this;
};
var Anim=function(){
...
};
Anim.
method('start',function(){
...
}).
method('stop',function(){
...
});
1.提供一种规定必需方法的手段;提供一种检查这些方法是否确实得到实现的手段;在结果为否定时能提供有用的错误信息。
2.谨慎地使用接口Interface类有助于创建更健壮的类和更稳定的代码。
信息隐藏原则有助于减轻系统中两个参与者之间的依赖性。两个参与者必须通过明确的通道传送信息。
信息隐藏是目的,而封装则是借以达到这个目的的技术。
封装:对对象的内部数据表现形式和实现细节进行隐藏。
封装是面向对象的设计的基石。
(1)保护内部数据的完整性。
(2)对象的重构变得轻松。
(3)弱化模块间的耦合,提高了对象的重用性。
(4)使用私用变量避免命名空间冲突。
(1)很难进行单元测试。
(2)不得不与复杂的作用域链打交道,这会使错误调试更加困难。
(3)过度封装。
(4)实现封装的难度大。
//超类Person
function Person(name)={
this.name=name;
}
Person.prototype.getName=function(){
return this.name;
}
//子类Author
function Author(name,books){
Person.call(this,name);
this.books=books;
}
利用访问属性时从本对象开始查找,找不到再往该对象的prototype上找,找不到再找往根节点找这个特性,可将父类赋值给本对象的prototype。
举例:
Author.prototype=new Person();//设置原型链,将子类的prototype设置为指向超类的一个实例
Author.prototype.constructor=Author;
Author.prototype.getBooks=function(){
return this.books;
}
简化类的声明,将派生子类的整个过程包装在一个函数中,基于一个给定的类结构创建一个新的类。
function extend(subClass,superClass){
var F=function(){};
F.prototype=superClass.prototype;
subClass.prototype=new F();
subClass.prototype.constructor=subClass;
}
举例:
extend(Author,Person);
Author.prototype.getBooks=function(){
return this.books;
}
弱化超类与子类的耦合,可以为子类添加一个superClass属性存放超类的原型对象。使用superClass便于在子类中重定义超类的方法。
function extend(subClass,superClass){
var F=function(){};
F.prototype=superClass.prototype;
subClass.prototype=new F();
subClass.prototype.constructor=subClass;
subClass.superClass=superClass.prototype;
if(superClass.prototype.constructor==Object.prototype.constructor){
superClass.prototype.constructor=superClass;
}
}
定义父类的结构——定义一个对象;使用clone函数复制一个新对象出来,整个新对象就是子类。
使用clone函数创建一个原型对象为超类的空对象。在这个空对象中查找某个方法或属性时,如果找不到,那么查找过程会在其原型对象中继续进行。
举例:
//超类
var Person={
name:'default name',
getName:function(){
return this.name;
}
}
//子类
var reader=clone(Person);
对继承而来的成员的读写不对等性。易改动原型对象。
function clone(object){
function F(){}
F.prototype=object;
return new F();
}
有风险。
更节约内存。
如果想把一个函数用到多个类中,可以通过扩充的方式让这些类共享该函数。
先创建一个包含各种通用方法的类,然后再用它扩充其他类。
mixin class通常不会被实例化或直接调用,其存在的目的只是向其他类提供自己的方法。
可以被视为多亲继承(multiple inheritance)在js中的一种实现方式。
function augment(receivingClass,givingClass){
for(methodName in givingClass.prototype){
if(!receivingClass.prototype[methodName]){
receivingClass.prototype[methodName]=givingClass.prototype[methodName];
}
}
}
用一个for…in循环遍历予类(givingClass)的prptotype中的每一个成员,并将其添加到受类(receivingClass)的prototype中。如果受类中已经存在同名成员,则跳过这个成员,转而处理下一个。受类中的成员不会被改写。
试图模仿C++和Java等面向对象语言中类的继承方式。最适合内存效率要求不高,程序员不熟悉不太知名的继承方式的场合。
用这种方法创建的对象有较高的内存效率,因为会共享那些未被改写的属性和方法。
适合彼此有较大差异的类共享一些通用方法。
这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。
划分命名空间,减少全局变量的数目
在分支(branching)技术中用来封装浏览器之间的差异
把代码组织得更为一致,提高可读性和可维护性
划分命名空间
用作特定网页专用代码的包装器的单体
拥有私用成员的单体
示例:
var Singleton={
attribute1:true,
attribute2:10,
method1:function(){},
method2:function(arg){}
}
这个示例与普通对象字面量没什么不同,可以被修改。
按传统定义:单体是一个只能被实例化一次并且可以通过一个众所周知的访问点访问的类。
广义定义:单体是一个用来划分命名空间并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。
对于资源密集型的或配置开销甚大的单体,更合理的做法是将其实例化推迟到需要使用它的时候。这种技术称为惰性加载(lazy loading)。常用语必须加载大量数据的单体。
对惰性加载单体的访问必须借助于一个getInstance()
方法,检查该单体是否已经被实例化,若还没有就创建并返回其实例。
Singleton.getInstance().methodName();//惰性加载单体
Singleton.methodName();//普通单体
使用闭包创建的单体,将闭包内所有代码放到名为constructor的方法中,constructor即为私有方法。
单体中抛出getInstance公有方法,控制单体类实例化时机,它要做2件事:判断该类是否已经被实例化;如果已经实例化过,那就返回这个实例。
通过一个私有属性uniqueInstance记录该单体类是否被实例化过,若uniqueInstance==‘undefined’,则赋值uniqueInstance=constructor();
复杂,创建这种单体的代码并不直观。
命名空间长,例如MyNamespace.Singleton.methodName()
。
是一种用来把浏览器间的差异封装到在运行期间进行设置的动态方法中的技术。
在脚本加载时一次性地确定针对特定浏览器的代码,在之后调用该方法时不必再运行检查代码,直接返回第一次的结果,提高效率。
具有分支的单体示例:
MyNamespace.Singleton=(function(){
var objectA={
method1:function(){...},
method2:function(){...}
};
var objectB={
method1:function(){...},
method2:function(){...}
};
return (someCondition)?objectA:objectB;
})();
objectA和objectB拥有相同的方法,只是实现过程不同。根据条件确定选择哪个分支。
这种条件通常是某种能力检测的结果,意在确保运行代码的js环境实现了所需要的特性。
在考虑是否使用分支时,需要在程序运行时间和占用内存这两方面权衡。是个分支对象较小而判断使用哪个分支的开销较大的情况。
return this;
优点:
消除对象间的耦合。
在派生子类时也提供了更大的灵活性。
缺点:
如果根本不可能另外换用一个类,或者不需要在运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。
(大多数类最好使用new关键字和构造函数公开地进行实例化;工厂方法可以在重构代码时使用。)
将抽象与其实现隔离开来,以便二者独立变化。
在设计一个js api时,可以用这个模式来弱化它与使用它的类和对象之间的耦合。
这种模式对于js中常见的事件驱动的变成大有裨益。
最常见和实际的应用场合之一是事件监听器的回调函数:
addEvent(element,'click',getBeerByIdBridge);
function getBeerByIdBridge(e){
getBeerById(this.id,function(beer){
console.log('Requested Beer:'+beer);
})
}
getBeerByIdBridge就是连接监听器和回调的桥。
优点:
缺点:
每使用一个桥接元素都要增加一次函数调用,对应用程序的性能会有一些负面影响。
提高了系统的复杂程度
如果一个桥接函数被用于连接2个函数,而其中某个函数根本不会在桥接函数之外被调用,那么此时这个桥接函书就是不必要的,可删除。
组合对象(集合:中间人,循环/递归调用叶对象方法,使得外部只需调用组合对象的一个方法)、叶对象
把一批子对象组织为树形结构,只要一条命令就可以操作树中的所有对象。
这种模式特别适合于动态的HTML用户界面,可以在不知道用户界面的最终格局的情况下进行开发。
优点:
缺点:
添加一些便利方法(这种方法是对原有的一些方法的组合利用)。在幕后进行错误检查、浏览器兼容性。是一种组织性的模式。
反复成组出现的代码
应对js内置函数在不同浏览器中的不同表现
优点:
缺点:
用于在现有借口和不兼容的类之间进行适配。这种模式的对象又叫“包装器”,因为是在用一个新的接口包装另一个对象。
适配器所适配的两个方法执行的应该是类似的任务。协调两个不同的接口。
优点:
缺点:
关键特点是能够与其组件互换使用。这样的好处之一就是可以透明地用新对象装饰现有系统中的对象,而这并不会改变代码中的其他东西。
装饰对象的类继承自原系统的类,定义好与原类同名的默认方法。
装饰对象的子类原型式继承自装饰对象的类,并重写同名方法。
调用时先创建原对象,再将原对象作为组件传入装饰对象即可。
可以使用多个装饰者
举例自行车售卖代码:
//原对象类:Bicycle(自行车)
//装饰对象类:BicycleDecorator(自行车装饰类)
//第一步
var BicycleDecorator = function(bicycle){
Interface.ensureImplements(bicycle,Bicycle);
this.bicycle = bicycle;
}
BicycleDecorator.prototype = {
assemble:function(){
return tis.bicycle.assemble;
},
wash:...
ride:...
repair:...
getPrice:function(){
return this.bicycle.getPrice();
}
};
//第二步
//装饰对象类:HeadlightDecorator(自行车带前灯)
var HeadlightDecorator = function(bicycle){
HeadlightDecorator.superclass.constructor.call(this,bicycle);
}
extend(HeadlightDecorator,BicycleDecorator);
HeadlightDecorator.prototype.getPrice = function(){
return this.bicycle.getPrice()+15.00;
};
//装饰对象类:TaillightDecorator(自行车带尾灯)
var TaillightDecorator = function(bicycle){
TaillightDecorator.superclass.constructor.call(this,bicycle);
}
extend(TaillightDecorator,BicycleDecorator);
TaillightDecorator.prototype.getPrice = function(){
return this.bicycle.getPrice()+9.00;
};
//第三步
var myBicycle = new Bicycle();
myBicycle = new HeadlightDecorator(myBicycle);
myBicycle = new TaillightDecorator(myBicycle);
alert(myBicycle.getPrice());//价格会加上前灯和尾灯
以下“方法”指的是继承自的组件的方法。
如果必须确保某种特定顺序,那么可以为此使用工厂对象。
将装饰内容放到工厂对象的创建方法中。
自行车售卖举例:
//原对象类:Bicycle
var BicycleShop = function() {};
BicycleShop.prototype.createBicycle = function(options){
var bicycle = new Bicycle();
for(var i = 0, len = options.length; i < len; i++){
var decorator = BicycleShop.options[options[i].name];
if(typeof decorator !== 'function'){
throw new Error('装饰者'+options[i].name+'不存在');
}
var argument = options[i].arg;
bicycle = new decorator(bicycle,argument);
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle();
}
BicycleShop.options = {
'headlight':HeadlightDecorator,
'taillight':TaillightDecorator
};
//调用
var bicycleShop = new BicycleShop();
var myBicycle = bicycleShop.createBicycle([
{name:'headlight'},{name:'taillight'}
]);
使用工厂方法可以直接使用它创建出来的对此昂就行,不必关心它是一个自行车对象还是一个装饰者对象。
工厂方法的装饰者模式适用于对选件需要排序的情况。
在对另一个函数的输出应用某种格式或执行某种转换这方面很有用处。
举例:
//将被包装者的返回结果改为大写形式
function upperCaseDecorator(func){
return function(){
return func.apply(this,arguments).toUpperCase();
}
}
//使用装饰者
function getDate(){
return new Date().toString();
}
getDateCaps = upperCaseDecorator(getDate);
//调用
alert(getDate());//Wed Sep 26 2007 20:11:02 GMT-0700(PDT)
alert(getDateCaps());//WED SEP 26 2007 20:11:02 GMT-0700(PDT)
需要为类增添特性或职责,而从该类派生子类的解决办法并不实际的话,就应该使用装饰者模式。
需要为对象增添特性而又不想改变使用该对象的代码的话,也可使用装饰者模式。
优点:
在运行期间动态为对象增添特性或职责。
运作过程透明,即可以用它包装其他对象,然后继续按之前使用那些对象的方法来使用它。
缺点:
是一种优化模式,目的在于提高系统使用内存的效率。
优点:
缺点:
创建另一个对象–代理,对本体对象的方法访问进行权限控制。不会修改或添加本体对象的方法。
与普通代理的关键区别在于,虚拟代理不会立即创建本体的实例,直到有方法被调用时才真正执行本体的实例化。
_initializeLibrary:function(){
if(this.library===null){
this.library=new PublicLibrary();
}
}
用于访问位于另一个环境中的对象。不适合js中使用。一种更有可能的用途是控制对其他语言中的本体的访问。
通常用来根据客户的身份控制对特定方法的访问。不适合js中使用。
优点:
缺点:
远程代理获取远程资源的请求响应比访问本地资源要慢很多。而且需要和回调函数结合使用,增加了代码的复杂性。另外只有能够与远程资源通信的条件下才能工作。
使项目增加了不必要的复杂性和代码。
多对多的关系
实质是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知。(addEventListener)
把人的行为和应用程序的行为分开。如事件监听器。
优点:
基于行为的应用程序。让观察对象借助一个事件监听器替你处理各种行为并将信息委托给它的所有订阅者,从而降低内存消耗和提高互动性能。
缺点:
创建可观察对象会有加载时间的开销。可以使用惰性加载技术(把新的可观察对象的实例化推迟到需要发送事件通知的时候)。
主要用途:把调用对象(用户界面、API和代理等)与实现操作的对象隔离开。
这是一种用来封装单个操作的结构型模式。
优点:
提高程序的模块化程度和灵活性。
实现取消和状态恢复等复杂的有用特性。
缺点:
可以用来消除请求的客户和处理程序之间的耦合。
通过实现一个由隐式地对请求进行处理的对象组成的链而做到的,链中的每个对象可以处理请求,也可以将其传给下一个对象。
js事件委托
优点:
缺点:
很多设计模式没有看懂,并且两者之间混淆,还需要继续学习和实践。