高阶函数是指至少满足下列条件之一的函数:
1) 函数可以作为参数被传递
2) 函数可以作为参数输出。
AOP:
1)AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。
单一职责原则:
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
单例模式的定义是:
1)保证一个类仅有一个实例
2)并提供一个访问它的全局访问点
var a={}
//或者
var Singleton = function( name ){
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function(){
alert ( this.name );
};
Singleton.getInstance = function( name ){
if ( !this.instance ){
this.instance = new Singleton( name );
}
return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true
虽然现在达到了惰性的目的,但失去了单例的效果。当我们每次点击登录按钮的时候,都会创建一个新的登录浮窗div。虽然我们可以在点击浮窗上的关闭按钮时(此处未实现)把这个浮窗从页面中删除掉,但这样频繁地创建和删除节点明显是不合理的,也是不必要的。
1)这段代码仍然是违反单一职责原则的,创建对象和管理单例的逻辑都放在createLoginLayer对象内部。
2)如果我们下次需要创建页面中唯一的iframe,或者script 标签,用来跨域请求数据,就必须得如法炮制,把createLoginLayer 函数几乎照抄一遍
现在我们就把如何管理单例的逻辑从原来的代码中抽离出来,这些逻辑被封装在getSingle函数内部,创建对象的方法fn 被当成参数动态传入getSingle 函数:
var getSingle = function( fn ){
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
};
策略模式:
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式是一种常用且有效的设计模式,可以用于计算奖金、缓动动画、表单校验等例子来加深大家对策略模式的理解。
下面是策略模式的几个有点:
1)策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
2) 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy 中,使得它们易于切换,易于理解,易于扩展。
3) 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
4) 在策略模式中利用组合和委托来让Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
代理模式:
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。比如,明星都有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬都谈好之后,再把合同交给明星签。代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
在JavaScript 开发中最常用的是虚拟代理和缓存代理:
//虚拟代理
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
}
}
})();
var proxyImage = (function () {
var img = new Image;
img.onload = function () {
myImage.setSrc(this.src);
}
return {
setSrc: function (src) {
myImage.setSrc('file:// /C:/Users/svenzeng/Desktop/loading.gif');
img.src = src;
}
}
})();
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');
//缓存代理
var mult=function(){
console.log("开始计算乘积");
var a=1;
for(var i=0;i
输出结果:
开始计算乘积
24
24
开始计算乘积
10
10
迭代器模式:
指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。
var each = function (ary, callback) {
for (var i = 0, l = ary.length; i < l; i++) {
if (callback(i, ary[i]) === false) { // callback 的执行结果返回false,提前终止迭代
break;
}
}
};
each([1, 2, 3, 4, 5], function (i, n) {
if (n > 3) { // n 大于3 的时候终止循环
return false;
}
console.log(n); // 分别输出:1, 2, 3
});
输出结果:
1
2
3
发布—订阅模式
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式。
var Events = {
clientList: {},
listen: function (key, fn) {
var fns = this.clientList[key]
if (!fns) {
fns = this.clientList[key] = [];
}
if (fn) {
fns.push(fn);
}
},
trigger: function () {
var key = [].shift.call(arguments);
var fns = this.clientList[key];
if (!fns || fns.length == 0) {
return false;
}
for (var i = 0, ary; ary = fns[i++];) {
ary.apply(this, arguments);
}
},
remove: function (key, fn) {
var fns = this.clientList[key];
if (!fns || fns.length == 0) {
return false;
}
if (!fn) {
fns.length = 0;
} else {
for (var i = 0; i < fns.length; i++) {
if (fns[i] == fn) {
fns.splice(i, 1);
}
}
}
}
}
var header = (function () { // header 模块
Events.listen('loginSucc', function (data) {
header.setAvatar(data.avatar);
});
return {
setAvatar: function (data) {
console.log(data+'设置header 模块的头像');
}
}
})();
var nav = (function () { // nav 模块
Events.listen('loginSucc', function (data) {
nav.setAvatar(data.avatar);
});
return {
setAvatar: function (data) {
console.log(data+'设置nav 模块的头像');
}
}
})();
//用setTimeout模拟真实操作
setTimeout(function(){
Events.trigger('loginSucc',{avatar:"登录成功开始"});
},3000);
//下面是真实项目请求后操作
// $.ajax('http:// xxx.com?login', function (data) { // 登录成功
// login.trigger('loginSucc', data); // 发布登录成功的消息
// });
输出结果:
登录成功开始设置header 模块的头像
登录成功开始设置nav 模块的头像
//也可以先发布,后订阅下面是简单js部分
var Events = {
clientList: {},
cache: {},
listen: function (key, fn) {
var fns = this.clientList[key]
if (!fns) {
fns = this.clientList[key] = [];
}
if (fn) {
fns.push(fn);
}
if (JSON.stringify(this.cache) != "{}") {
this._trigger(key, fn);
this.cache = [];
};
},
_trigger: function (key, fn) {
var arys = this.cache[key];
if (!fn || fn.length == 0) {
return false;
}
for (var i = 0, ary; ary = arys[i++];) {
fn.apply(this, ary);
}
},
trigger: function () {
var key = [].shift.call(arguments);
var fns = this.clientList[key];
if (typeof ss != "function" || typeof ss != "undefined") {
if (!this.cache[key]) {
this.cache[key] = [];
}
this.cache[key].push(arguments);
}
if (!fns || fns.length == 0) {
return false;
}
for (var i = 0, ary; ary = fns[i++];) {
ary.apply(this, arguments);
}
},
remove: function (key, fn) {
var fns = this.clientList[key];
if (!fns || fns.length == 0) {
return false;
}
if (!fn) {
fns.length = 0;
} else {
for (var i = 0; i < fns.length; i++) {
if (fns[i] == fn) {
fns.splice(i, 1);
}
}
}
}
}
Events.trigger('click', 1);
Events.listen('click', function (a) {
console.log(a); // 输出:1
});
输出结果:
1
命令模式
命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品,命令模式在JavaScript 语言中是一种隐形的模式
。
命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
。
1)简单的命令模式案例:
//html
//不是命令模式实现
var bindClick = function (button, func) {
button.onclick = func;
};
var MenuBar = {
refresh: function () {
console.log('刷新菜单界面');
}
};
var SubMenu = {
add: function () {
console.log('增加子菜单');
},
del: function () {
console.log('删除子菜单');
}
};
bindClick(button1, MenuBar.refresh);
bindClick(button2, SubMenu.add);
bindClick(button3, SubMenu.del);
//命令模式实现
var MenuBar = {
refresh: function () {
console.log('刷新菜单目录');
}
};
var SubMenu = {
add: function () {
console.log('增加子菜单');
},
del: function () {
console.log('删除子菜单');
}
};
var setCommand = function (button, command) {
button.onclick = function () {
command.execute();
}
};
var RefreshMenuBarCommand = function (receiver) {
this.receiver = receiver;
};
RefreshMenuBarCommand.prototype.execute = function () {
this.receiver.refresh();
};
var AddSubMenuCommand = function (receiver) {
this.receiver = receiver;
};
AddSubMenuCommand.prototype.execute = function () {
this.receiver.add();
};
var DelSubMenuCommand = function (receiver) {
this.receiver = receiver;
};
DelSubMenuCommand.prototype.execute = function () {
console.log('删除子菜单');
}
var button1 = document.getElementById('button1'),
button2 = document.getElementById('button2'),
button3 = document.getElementById('button3');
var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
setCommand(button1, refreshMenuBarCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);
2)撤消和重做
很多时候,我们需要撤销一系列的命令。比如在一个围棋程序中,现在已经下了10 步棋,我们需要一次性悔棋到第5 步。在这之前,我们可以把所有执行过的下棋命令都储存在一个历史列表中,然后倒序循环来依次执行这些命令的undo 操作,直到循环执行到第5 个命令为止。然而,在某些情况下无法顺利地利用undo 操作让对象回到execute 之前的状态。比如在一个Canvas 画图的程序中,画布上有一些点,我们在这些点之间画了N 条曲线把这些点相互连接起来,当然这是用命令模式来实现的。但是我们却很难为这里的命令对象定义一个擦除某条曲线的undo 操作,因为在Canvas 画图中,擦除一条线相对不容易实现。这时候最好的办法是先清除画布,然后把刚才执行过的命令全部重新执行一遍,这一点同样可以利用一个历史列表堆栈办到。记录命令日志,然后重复执行它们,这是逆转不可逆命令的一个好办法。
//html
//js
var Ryu = {
attack: function () {
console.log("攻击");
},
defense: function () {
console.log("防御");
},
jump: function () {
console.log("跳跃");
},
crouch: function () {
console.log("蹲下");
}
}
var commands = {
"119": "jump", // W
"115": "crouch", // S
"97": "defense", // A
"100": "attack" // D
};
var commandStack=[];
var makeCommand=function(receive,state){
return function(){
return receive[state]();
}
}
document.onkeypress=function(e){
var keyCode=e.keyCode;
var command=makeCommand(Ryu,commands[keyCode]);
if(command){
command();
commandStack.push(command);
}
}
document.getElementById("replay").onclick=function(){
var command=null;
for(var i=0;i
宏命令
宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令。
var closeDoorCommand = {
execute: function () {
console.log('关门');
}
};
var openPcCommand = {
execute: function () {
console.log('开电脑');
}
};
var openQQCommand = {
execute: function () {
console.log('登录QQ');
}
};
var MacroCommand=function(){
return {
commandsList:[],
add:function(command){
this.commandsList.push(command);
},
execute:function(){
for(var i=0,command;command=this.commandsList[i++];){
command.execute();
}
}
}
}
var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();
输出结果:
关门
开电脑
登录QQ
组合模式
组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
1)提供了一种遍历树形结构的方案,通过调用组合对象的execute 方法,程序会递归调用组合对象下面的叶对象的execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、打开电脑、登录QQ 这几件事情。组合模式可以非常方便地描述对象部分整体层次结构。
2)利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。
一些值得注意的地方
1)组合模式不是父子关系
组合模式的树型结构容易让人误以为组合对象和叶对象是父子关系,这是不正确的。组合模式是一种HAS-A(聚合)的关系,而不是IS-A。组合对象包含一组叶对象,但Leaf并不是Composite 的子类。组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。为了方便描述,本章有时候把上下级对象称为父子节点,但大家要知道,它们并非真正意义上的父子关系。
2)对叶对象操作的一致性
组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。比如公司要给全体员工发放元旦的过节费1000 块,这个场景可以运用组合模式,但如果公
司给今天过生日的员工发送一封生日祝福的邮件,组合模式在这里就没有用武之地了,除非先把今天过生日的员工挑选出来。只有用一致的方式对待列表中的每个叶对象的时候,才适合使用组合模式。
3)双向映射关系
发放过节费的通知步骤是从公司到各个部门,再到各个小组,最后到每个员工的邮箱里。这本身是一个组合模式的好例子,但要考虑的一种情况是,也许某些员工属于多个组织架构。比如某位架构师既隶属于开发组,又隶属于架构组,对象之间的关系并不是严格意义上的层次结构,在这种情况下,是不适合使用组合模式的,该架构师很可能会收到两份过节费。这种复合情况下我们必须给父节点和子节点建立双向映射关系,一个简单的方法是给小组和员工对象都增加集合来保存对方的引用。但是这种相互间的引用相当复杂,而且对象之间产生了过多的耦合性,修改或者删除一个对象都变得困难,此时我们可以引入中介者模式来管理这些对象。
4) 用职责链模式提高组合模式性能
在组合模式中,如果树的结构比较复杂,节点数量很多,在遍历树的过程中,性能方面也许表现得不够理想。有时候我们确实可以借助一些技巧,在实际操作中避免遍历整棵树,有一种现成的方案是借助职责链模式。职责链模式一般需要我们手动去设置链条,但在组合模式中,父对象和子对象之间实际上形成了天然的职责链。让请求顺着链条从父对象往子对象传递,或者是反过来从子对象往父对象传递,直到遇到可以处理该请求的对象为止,这也是职责链模式的经典运用场景之一。
var openAcCommand = {
execute: function () {
console.log('打开空调');
}
};
var openTvCommand = {
execute: function () {
console.log('打开电视');
}
};
var openSoundCommand = {
execute: function () {
console.log('打开音响');
}
};
var closeDoorCommand = {
execute: function () {
console.log('关门');
}
};
var openPcCommand = {
execute: function () {
console.log('开电脑');
}
};
var openQQCommand = {
execute: function () {
console.log('登录QQ');
}
};
var MacroCommand = function () {
return {
commandsList: [],
add: function (command) {
this.commandsList.push(command);
},
execute: function () {
for (var i = 0, command; command = this.commandsList[i++];) {
command.execute();
}
}
}
}
var macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);
var macroCommand2 = MacroCommand();
macroCommand2.add(closeDoorCommand);
macroCommand2.add(openPcCommand);
macroCommand2.add(openQQCommand);
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
var setCommand = (function (command) {
document.getElementById('button').onclick = function () {
command.execute();
}
})(macroCommand);
输出结果:
打开空调
打开电视
打开音响
关门
开电脑
登录QQ
组合模式的例子——扫描文件夹
//js
var Folder = function (name) {
this.name = name;
this.files = [];
}
Folder.prototype.add = function (file) {
this.files.push(file);
}
Folder.prototype.scan = function () {
console.log("开始扫描文件夹1:" + this.name);
for (var i = 0, file; file = this.files[i++];) {
file.scan();
}
}
var File = function (name) {
this.name = name;
}
File.prototype.add = function (file) {
throw new Error('文件下面不能再添加文件');
}
File.prototype.scan = function () {
console.log("开始扫描文件夹:" + this.name);
}
var folder = new Folder('学习资料');
var folder1 = new Folder('JavaScript');
var folder2 = new Folder('jQuery');
var file1 = new File('JavaScript 设计模式与开发实践');
var file2 = new File('精通jQuery');
var file3 = new File('重构与模式')
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);
folder.scan();
输出结果:
开始扫描文件夹1:学习资料
开始扫描文件夹1:JavaScript
开始扫描文件夹:JavaScript 设计模式与开发实践
开始扫描文件夹1:jQuery
开始扫描文件夹:精通jQuery
开始扫描文件夹:重构与模式
引用父对象
组合对象保存了它下面的子节点的引用,这是组合模式的特点,此时树结构是从上至下的。但有时候我们需要在子节点上保持对父节点的引用,比如在组合模式中使用职责链时,有可能需要让请求从子节点往父节点上冒泡传递。还有当我们删除某个文件的时候,实际上是从这个文件所在的上层文件夹中删除该文件的。
现在来改写扫描文件夹的代码,使得在扫描整个文件夹之前,我们可以先移除某一个具体的文件。
var Folder = function (name) {
this.name = name;
this.parent=null;
this.files = [];
}
Folder.prototype.add = function (file) {
file.parent=this;
this.files.push(file);
}
Folder.prototype.scan = function () {
console.log("开始扫描文件夹1:" + this.name);
for (var i = 0, file; file = this.files[i++];) {
file.scan();
}
}
Folder.prototype.remove=function(){
if(!this.parent){
return false;
}else{
for(var i=0,len=this.parent.files.length;i
输出结果:
开始扫描文件夹1:学习资料
开始扫描文件夹1:jQuery
开始扫描文件夹:精通jQuery
开始扫描文件夹:重构与模式
模板方法模式
模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
var Beverage = function () { };
Beverage.prototype.boilWater = function () {
console.log('把水煮沸');
};
Beverage.prototype.brew = function () {
throw new Error('子类必须重写brew 方法');
};
Beverage.prototype.pourInCup = function () {
throw new Error('子类必须重写pourInCup 方法');
};
Beverage.prototype.addCondiments = function () {
throw new Error('子类必须重写addCondiments 方法');
};
Beverage.prototype.customerWantsCondiments = function () {
return true; // 默认需要调料
};
Beverage.prototype.init = function () {
this.boilWater();
this.brew();
this.pourInCup();
if (this.customerWantsCondiments()) { // 如果挂钩返回true,则需要调料
this.addCondiments();
}
};
var CoffeeWithHook = function () { };
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function () {
console.log('用沸水冲泡咖啡');
};
CoffeeWithHook.prototype.pourInCup = function () {
console.log('把咖啡倒进杯子');
};
CoffeeWithHook.prototype.addCondiments = function () {
console.log('加糖和牛奶');
};
CoffeeWithHook.prototype.customerWantsCondiments = function () {
return window.confirm('请问需要调料吗?');
};
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();
输出结果:
把水煮沸
用沸水冲泡咖啡
把咖啡倒进杯子
加糖和牛奶
好莱坞原则:别调用我,我调用你
var Beverage = function (param) {
var boilWater = function () {
console.log('把水煮沸');
};
var brew = param.brew || function () {
throw new Error('必须传递brew 方法');
};
var pourInCup = param.pourInCup || function () {
throw new Error('必须传递pourInCup 方法');
};
var addCondiments = param.addCondiments || function () {
throw new Error('必须传递addCondiments 方法');
};
var F = function () { };
F.prototype.init = function () {
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};
var Coffee = Beverage({
brew: function () {
console.log('用沸水冲泡咖啡');
},
pourInCup: function () {
console.log('把咖啡倒进杯子');
},
addCondiments: function () {
console.log('加糖和牛奶');
}
});
var Tea = Beverage({
brew: function () {
console.log('用沸水浸泡茶叶');
},
pourInCup: function () {
console.log('把茶倒进杯子');
},
addCondiments: function () {
console.log('加柠檬');
}
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();
输出结果:
把水煮沸
用沸水冲泡咖啡
把咖啡倒进杯子
加糖和牛奶
把水煮沸
用沸水浸泡茶叶
把茶倒进杯子
加柠檬
享元模式
享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情。
1)内部状态存储于对象内部。
2) 内部状态可以被一些对象共享。
3) 内部状态独立于具体的场景,通常不会改变。
4) 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时
便可以使用享元模式:
1) 一个程序中使用了大量的相似对象。
2) 由于使用了大量对象,造成很大的内存开销。
3) 对象的大多数状态都可以变为外部状态。
4) 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
var Model = function (sex) {
this.sex = sex;
}
Model.prototype.takePhoto = function () {
console.log('sex=' + this.sex + 'underwear=' + this.underwear);
}
var maleModel = new Model('male'),
femaleModel = new Model('female');
for (var i = 1; i <= 50; i++) {
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
};
对象池实现
var objectPoolFactory = function (createObjFn) {
var objectPool = [];
return {
create: function () {
var obj = objectPool.length === 0 ?
createObjFn.apply(this, arguments) : objectPool.shift();
return obj;
},
recover: function (obj) {
objectPool.push(obj);
}
}
};
var iframeFactory = objectPoolFactory(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = function () {
iframe.onload = null; // 防止iframe 重复加载的bug
iframeFactory.recover(iframe); // iframe 加载完成之后回收节点
}
return iframe;
});
var iframe1 = iframeFactory.create();
iframe1.src = 'http:// baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http:// QQ.com';
setTimeout(function () {
var iframe3 = iframeFactory.create();
iframe3.src = 'http:// 163.com';
}, 3000);
职责链模式
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
var order = function (orderType, pay, stock) {
if (orderType === 1) { // 500 元定金购买模式
if (pay === true) { // 已支付定金
console.log('500 元定金预购, 得到100 优惠券');
} else { // 未支付定金,降级到普通购买模式
if (stock > 0) { // 用于普通购买的手机还有库存
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 2) { // 200 元定金购买模式
if (pay === true) {
console.log('200 元定金预购, 得到50 优惠券');
} else {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
}
else if (orderType === 3) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
}
};
order(1, true, 500); // 输出: 500 元定金预购, 得到100 优惠券
但这远远算不上一段值得夸奖的代码。order 函数不仅巨大到难以阅读,而且需要经常进行修改。现在我们采用职责链模式重构这段代码。
// 500 元订单
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购, 得到100 优惠券');
} else {
order200(orderType, pay, stock); // 将请求传递给200 元订单
}
};
// 200 元订单
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购, 得到50 优惠券');
} else {
orderNormal(orderType, pay, stock); // 将请求传递给普通订单
}
};
// 普通购买订单
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买, 无优惠券');
} else {
console.log('手机库存不足');
}
};
// 测试结果:
order500(1, true, 500); // 输出:500 元定金预购, 得到100 优惠券
order500(1, false, 500); // 输出:普通购买, 无优惠券
order500(2, true, 500); // 输出:200 元定金预购, 得到500 优惠券
order500(3, false, 500); // 输出:普通购买, 无优惠券
order500(3, false, 0); // 输出:手机库存不足
目前已经有了不小的进步,但我们不会满足于此,虽然已经把大函数拆分成了互不影响的3个小函数,但可以看到,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中,这依然是违反开放封闭原则的,如果有天我们要增加300 元预订或者去掉200 元预订,意
味着就必须改动这些业务函数内部。就像一根环环相扣打了死结的链条,如果要增加、拆除或者
移动一个节点,就必须得先砸烂这根链条。我们采用一种更灵活的方式,来改进上面的职责链模式,目标是让链中的各个节点可以灵活拆分和重组。
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购,得到100 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购,得到50 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买,无优惠券');
} else {
console.log('手机库存不足');
}
};
var Chain = function (fn) {
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function (successor) {
return this.successor = successor;
};
Chain.prototype.passRequest = function () {
var ret = this.fn.apply(this, arguments);
if (ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret;
};
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);
chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到100 优惠券
chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到50 优惠券
chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券
chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足
var order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500 元定金预购,得到100 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200 元定金预购,得到50 优惠券');
} else {
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通购买,无优惠券');
} else {
console.log('手机库存不足');
}
};
Function.prototype.after = function (fn) {
var self = this;
return function () {
var ret = self.apply(this, arguments);
if (ret === 'nextSuccessor') {
return fn.apply(this, arguments);
}
return ret;
}
};
var order = order500.after(order200).after(orderNormal);
order(1, true, 500); // 输出:500 元定金预购,得到100 优惠券
order(2, true, 500); // 输出:200 元定金预购,得到50 优惠券
order(1, false, 500); // 输出:普通购买,无优惠券
中介者模式
中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者模式使网状的多对多关系变成了相对简单的一对多关系。
function Player(name){
this.name=name;
this.enemy=null;
}
Player.prototype.win=function(){
console.log(this.name+'won');
}
Player.prototype.lose=function(){
console.log(this.name+'lose');
}
Player.prototype.die=function(){
this.lose();
this.enemy.win();
}
var player1 = new Player( '皮蛋' );
var player2 = new Player( '小乖' );
player1.enemy = player2;
player2.enemy = player1;
player1.die();// 输出:皮蛋 lost、小乖 won
var players = [];
function Player(name, teamColor) {
this.name = name;
this.enemies = [];
this.state = 'live';
this.partners = [];
this.teamColor = teamColor;
}
Player.prototype.win = function () {
console.log(this.name + 'won');
}
Player.prototype.lose = function () {
console.log(this.name + 'lose');
}
Player.prototype.die = function () {
var all_dead = true;
this.state = 'dead';
for (var i = 0, play; play = this.partners[i++];) {
if (play.state == 'live') {
all_dead = false;
}
}
if (all_dead) {
this.lose();
for (var i = 0, play; play = this.partners[i++];) {
play.lose();
}
for (var i = 0, enemy; enemy = this.enemies[i++];) {
enemy.win();
}
}
}
var playerFactory = function (name, teamColor) {
var newPlay = new Player(name, teamColor);
for (var i = 0, play; play = players[i++];) {
if (play.teamColor == teamColor) {
newPlay.partners.push(play);
play.partners.push(newPlay);
} else {
newPlay.enemies.push(play);
play.enemies.push(newPlay);
}
}
players.push(newPlay);
return newPlay;
}
var player1 = playerFactory('皮蛋', 'red'),
player2 = playerFactory('小乖', 'red'),
player3 = playerFactory('宝宝', 'red'),
player4 = playerFactory('小强', 'red');
//蓝队:
var player5 = playerFactory('黑妞', 'blue'),
player6 = playerFactory('葱头', 'blue'),
player7 = playerFactory('胖墩', 'blue'),
player8 = playerFactory('海盗', 'blue');
player1.die();
player2.die();
player4.die();
player3.die();
执行结果:
选择颜色:
选择内存:
输入购买数量:
您选择了颜色:
您选择了内存:
您输入了数量:
装饰者模式
装饰者模式将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链。请求随着这条链依次传递到所有的对象,每个对象都有处理这条请求的机会。
var plane = {
fire: function () {
console.log('发射普通子弹');
}
}
var missileDecorator = function () {
console.log('发射导弹');
}
var atomDecorator = function () {
console.log('发射原子弹');
}
var fire1 = plane.fire;
plane.fire = function () {
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function () {
fire2();
atomDecorator();
}
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
var a = function () {
alert(1);
}
var _a = a;
a = function () {
_a();
alert(2);
}
a();
//弹出1,弹出2
window.onload = function () {
alert(1);
}
var _onload = window.onload || function () { };
window.onload = function () {
_onload();
alert(2);
}
//弹出1,弹出2
注释:此时_getElementById 是一个全局函数,当调用一个全局函数时,this 是指向window 的,而document.getElementById 方法的内部实现需要使用this 引用,this 在这个方法内预期是指向document,而不是window
Function.prototype.before = function (beforefn) {
var __self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的"代理"函数
beforefn.apply(this, arguments); // 执行新函数,且保证this 不被劫持,新函数接受的参数
// 也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
// 并且保证this 不被劫持
}
}
Function.prototype.after = function (afterfn) {
var __self = this;
return function () {
var ret = __self.apply(this, arguments);
afterfn.apply(this, arguments);
return ret;
}
};
window.onload = function () {
alert(1);
}
window.onload = (window.onload || function () { }).after(function () {
alert(2);
}).after(function () {
alert(3);
}).after(function () {
alert(4);
});
用户名:
密码:
注释:formSubmit 函数在此处承担了两个职责,除了提交ajax 请求之外,还要验证用户输入的合法性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。
用户名:
密码:
注释:现在的代码已经有了一些改进,我们把校验的逻辑都放到了validata 函数中,但formSubmit函数的内部还要计算validata 函数的返回值,因为返回值的结果表明了是否通过校验。
接下来进一步优化这段代码,使validata 和formSubmit 完全分离开来。首先要改写Function.prototype.before, 如果beforefn 的执行结果返回false,表示不再执行后面的原函数,代码如下:
用户名:
密码:
装饰者模式和第6 章代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对象动态加入行为。换句话说,代理模式强调一种关系(Proxy 与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理本体的引用,而装饰者模式经常会形成一条长长的装饰链。
在虚拟代理实现图片预加载的例子中,本体负责设置img 节点的src,代理则提供了预加载的功能,这看起来也是“加入行为”的一种方式,但这种加入行为的方式和装饰者模式的偏重点是不一样的。装饰者模式是实实在在的为对象增加新的职责和行为,而代理做的事情还是跟本体一样,最终都是设置src。但代理可以加入一些“聪明”的功能,比如在图片真正加载好之前,
状态模式
状态模式是一种非同寻常的优秀模式,它也许是解决某些需求场景的最好方法。虽然状态模式并不是一种简单到一目了然的模式(它往往还会带来代码量的增加),但你一旦明白了状态模式的精髓,以后一定会感谢它带给你的无与伦比的好处。