基础知识
设计模式
单例模式
什么是单例?
保证一个类只能创建一个实例,并且提供访问他的全局访问点
例如:登录按钮,点击创建一次登录浮框,再点击不会创建,会返回刚才创建的浮框。常见的应用场景:线程池、全局缓存、浏览器window对象
惰性单例特点
- 在需要的时候才创建
- 把管理单例和创建对象分为两个方法来实现
遵守单一责任原则
惰性单例案例:
//1.返回一个函数,用于惰性调用
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments)) }}
//2. 创建对象的方法(这个跟据具体情况创建)
var createDiv = function() {
let div = document.createElement("div");
div.style.display = "none";
div.innerText = "输入密码登录"
document.body.appendChild(div);
return div
}
//3.使用单例
var createSingleLayer = getSingle(createDiv);
//4. 调用单例方法
document.getElementById("btn").onclick = function() {
let div = createSingleLayer();
div.style.display = "block";
}
策略模式
什么是策略模式?
将一系列算法封装起来,为了以后可以互相替换使用,由策略类和context组成,context接受用户信息,然后将请求委托给策略类
(现实生活中,我们要去一个城市,交通方式就有:飞机、高铁、开车、大巴等,这些方式都能到达目的地,我们可以根据自身需求来选择一个策略)
策略模式特点
优点
- 策略模式利用组合、委托和多态等技术思想,可以有效的避免许多重条件选择语句
- 有弹性,遵守封闭/开发原则,将算法封装在独立的strategy中,使他易于切换、易于理解、易于扩展
- 复用方便
利用委托和组合来让context拥有执行算法的能力,这也是继承的一种更轻便的替代方案
缺点
- 会产生比较多的类和函数
要使用策略就要明白所有策略,违反知识最少化原则
案例
1. 计算奖金
//没有使用策略模式 function cuculateBounds(leave,salary){ if(leave==="A"){ return 4*salary }else if(leave ==="B"){ return 3*salary }else{ return 2*salary } } // 缺点: // 1、所有逻辑都在cuculateBounds函数体里面,要包含所有的if-else,体量庞大 // 2、缺乏弹性,如果要修改奖金等级A的系数为3.5,就要改变函数体,违反封闭开放与原则 // 3、复用性差,如果要复用则要用复制粘贴 //用策略模式改写 // 1. 将使用算法和算法分开,使用算法的方式不会变,算法会变,所以封装算法(封装变化) // 2.策略类-不同策略返回不同结果(多态) var strateies={ "A":function(salary){ return 4*salary; }, "B":function(salary){ return 3*salary; }, "C":function(salary){ return 2*salary; } } // 3.context 使用算法,接收请求,不执行操作,将请求委托给策略类(委托) var caculateBounds = function(leave,salary){ return strateies[leave](salary); } console.log(caculateBounds("A",2000))
2. 实现动画
<div id="box" style="position:absolute;width: 200px;height: 200px;background-color: cornflowerblue;"></div>
// 1.策略类-封装动画缓动算法 /**params * *开始位置 *要移动的距离 *消耗了多少时间 *总耗时 * */ var easing={ "linear":function(starPos,pos,time,duration){ return pos*time/duration + starPos; }, "easeIn":function( starPos,pos,time,duration){ return pos*(time/=duration)*time +starPos; } } // 2.动画类 // 利用定时器,没19毫秒一帧,更新dom节点样式 function Animate(dom){ this.dom = dom ; } // 接收4个参数 /** * 样式 * 移动目标位置 * 执行时间 * 缓动类型 * **/ Animate.prototype.start = function(propety,pos,duration,estype){ this.propety = propety; // 开始位置 this.startPos = this.dom.getBoundingClientRect()[propety] this.endPos = pos; this.startTime = +new Date; this.endTime = this.startTime+duration; this.duraPos = pos-this.startPos; this.easing = easing[estype]; this.duration = duration; var _that = this; var timeId = setInterval(()=>{ if(_that.step()===false){ // 清空定时器 clearInterval(timeId) timeId = null; } },19) } Animate.prototype.step = function(){ // 当前时间大于结束时间,返回false var nowTime =+new Date if(nowTime>=this.endTime){ // 校正位置 this.update(this.endPos) return false }else{ let pos = this.easing(this.startPos,this.duraPos,nowTime-this.startTime,this.duration) this.update(pos) } } Animate.prototype.update =function(val){ this.dom.style[this.propety] = val+'px' } // /////////////////////////////3.调用动画//////////////// var dom = document.getElementById("box"); var animate = new Animate(dom); // animate.start("top",500,3000,"linear") // animate.start("left",500,2000,"easeIn") animate.start("left",500,2000,"linear")
3. 验证表单
<form id="formpane"> 用户名:<input type="text" value="" id="userName" placeholder="请输入" /> 手机号:<input type="tel" value="" id="telphoneNum" /> 密码:<input type="password" value="" id="userPassword" /> <button type="submit">提交</button> </form>
// 多规则验证,满足条件,表单放行 /**多规则验证,满足条件,表单放行 * 规则1:用户名不能为空 * 规则2:手机格式正确 * 规则3:密码长度小于6 */ let regisform = document.getElementById("formpane"); /////////////////////////没有策略模式写法(我的常见写法)////////////////// // regisform.onsubmit = function(){ // // 用户名不能为空 // if(regisform.userName.value.length===0){ // console.log("用户名不能为空") // return false // } // else if(!/(^1[3|5|8][0-9]{9}$)/.test(regisform.telphoneNum.value)){ // console.log("手机格式不正确") // return false // }else if(regisform.userPassword.value.length>6){ // // 密码长度小于6 // console.log("密码长度小于6") // return false // } // alert("提交成功") // } /**该写法的启发 * 1、表单的值可以通过表单的dom.id(子id)例regisform.userName * 2、onsubmit函数体比较庞大,包含所有的if-else逻辑 * 3、缺乏弹性,当要修改验证规则时,需要改动内部逻辑,违反封闭开放原则 * 4、不易复用 */ // ///////////////////////////用策略模式改写//////////////////// // 1、创建一个策略类 var stargeies = { isNameEmpty: function (value, msg) { if (value.length === 0) return msg }, isNumberTrue: function (value, msg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return msg } }, isMinlen: function (value, min, msg) { if (value.length < min) return msg } } // 2、创建一个context的类用来接收用户的请求 function Invalidator() { this.catchs = []; } // 添加规则 Invalidator.prototype.add = function (dom, rules, msg) { var arr = rules.split(":"); this.catchs.push(function () { let starge = arr.shift();//删除数组第一个 let value = dom.value; arr.unshift(value);//在数组第一个插入 arr.push(msg) return stargeies[starge].apply(dom, arr); }) } // 执行规则返回结果 Invalidator.prototype.start = function () { // 这种方式遍历,当满足条件,退出循环 for (let index = 0; index < this.catchs.length; index++) { console.log(index); let msg = this.catchs[index](); if (msg) { return msg } } } // 3、用户调用规则,根据结果判断是否提交表单 // var invaliFunc = function(){ // var invalidator = new Invalidator(); // invalidator.add(regisform.userName,"isNameEmpty","用户名不能为空"); // invalidator.add(regisform.telphoneNum,"isNumberTrue","手机格式不正确"); // invalidator.add(regisform.userPassword,"isMinlen:8","密码长度不能小于8"); // return invalidator.start(); // } // regisform.onsubmit = function(){ // let value = invaliFunc() // if(value){ // console.log(value); // return false; // } // } // //////////////////////////////策略模式-表单验证多规则////////////// // 添加多规则 Invalidator.prototype.adds = function (dom, arrs) { arrs.forEach(element => { let { rules, msg } = element; let arr = rules.split(":"); this.catchs.push(function () { let starge = arr.shift();//删除数组第一个 let value = dom.value; arr.unshift(value);//在数组第一个插入 arr.push(msg) return stargeies[starge].apply(dom, arr); }) }); } var invaliFunc = function () { var invalidator = new Invalidator(); invalidator.adds(regisform.userName, [{ rules: "isNameEmpty", msg: "用户名不能为空" }, { rules: "isMinlen:6", msg: "用户名不能小于6" }]); invalidator.add(regisform.telphoneNum, "isNumberTrue", "手机格式不正确"); invalidator.add(regisform.userPassword, "isMinlen:8", "密码长度不能小于8"); return invalidator.start(); } regisform.onsubmit = function () { let value = invaliFunc() if (value) { console.log(value); return false; } }
4. 高阶函数实现隐形的策略模式(推荐使用)
<!-- 常见的策略模式。不会有策略类来存放策略方法 --> <script> function planA(params) { console.log("A"+params) } function planB(params) { console.log("B"+params) } function planC(params) { console.log("C"+params) } // 使用高阶函数的方式,参数传入函数,然后将事件委托到策略类中执行,多态,调用这个方法传入不同状态,返回不同结果 function caculateBounds(func,params){ func(params) } caculateBounds(planA,"欢迎使用A策略") </script>
代理模式
什么是代理模式?
代理模式是为一个对象提供一个代替品或占位符,意义是符合单一职责原则
代理模式特点
代理和本体接口一致性
案例
虚拟代理
虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建
1.图片预加载
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return function( src ){
imgNode.src = src;
}
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage( this.src );
}
return function( src ){
myImage( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
})();
proxyImage( 'http:// imgcache.qq.com/music// N/k/000GGDys0yA0Nk.jpg' );
2.合并HTTP请求
<body>
<input type="checkbox" id="1"></input>1
<input type="checkbox" id="2"></input>2
<input type="checkbox" id="3"></input>3
<input type="checkbox" id="4"></input>4
<input type="checkbox" id="5"></input>5
<input type="checkbox" id="6"></input>6
<input type="checkbox" id="7"></input>7
<input type="checkbox" id="8"></input>8
<input type="checkbox" id="9"></input>9
</body>
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的ID
timer; // 定时器
return function( id ){
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的ID 集合
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
3.惰性加载控制台
var miniConsole = (function(){
var cache = [];
var handler = function( ev ){
if ( ev.keyCode === 113 ){
var script = document.createElement( 'script' );
script.onload = function(){
for ( var i = 0, fn; fn = cache[ i++ ]; ){
fn();
}
};
script.src = 'miniConsole.js';
document.getElementsByTagName( 'head' )[0].appendChild( script );
document.body.removeEventListener( 'keydown', handler );// 只加载一次miniConsole.js
}
};
document.body.addEventListener( 'keydown', handler, false );
return {
log: function(){
var args = arguments;
cache.push( function(){
return miniConsole.log.apply( miniConsole, args );
});
}
}
})();
miniConsole.log( 11 ); // 开始打印log
// miniConsole.js 代码
miniConsole = {
log: function(){
// 真正代码略
console.log( Array.prototype.join.call( arguments ) );
}
}
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时存储,在下次运算时,如果传进来参数和之前一样,直接返回前面存储的运算结果
高阶函数动态创建代理
/**************** 计算乘积 *****************/
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
};
var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
发布-订阅模式
什么是发布订阅模式?
又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都将得到通知
特点
1、为时间上的解耦
2、为对象之间的解耦
案例
1、全局的发布-订阅对象
常用于中介、模块之间通信
var Event = (function(){
var clientList = {},
listen,
trigger,
remove;
listen = function( key, fn ){
if ( !clientList[ key ] ){
clientList[ key ] = [];
}
clientList[ key ].push( fn );
};
trigger = function(){
var key = Array.prototype.shift.call( arguments ),
fns = clientList[ key ];
if ( !fns || fns.length === 0 ){
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments );
}
};
remove = function( key, fn ){
var fns = clientList[ key ];
if ( !fns ){
return false;
}
if ( !fn ){
fns && ( fns.length = 0 );
}else{
for ( var l = fns.length - 1; l >=0; l-- ){
var _fn = fns[ l ];
if ( _fn === fn ){
fns.splice( l, 1 );
}
}
}
};
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
Event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
console.log( '价格= ' + price ); // 输出:'价格=2000000'
});
Event.trigger( 'squareMeter88', 2000000 ); // 售楼处发布消息
模块间使用
<body>
<button id="count">点我</button>
<div id="show"></div>
</body>
var a = (function(){
var count = 0;
var button = document.getElementById( 'count' );
button.onclick = function(){
Event.trigger( 'add', count++ );
}
})();
var b = (function(){
var div = document.getElementById( 'show' );
Event.listen( 'add', function( count ){
div.innerHTML = count;
});
})();
2、解决先发布后订阅(例如:qq离线消息)和命名空间冲突的全局发布-订阅模式
var Event = (function(){
var global = this,
Event,
_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function( ary, fn ){
var ret;
for ( var i = 0, l = ary.length; i < l; i++ ){
var n = ary[i];
ret = fn.call( n, i, n);
}
return ret;
};
_listen = function( key, fn, cache ){
if ( !cache[ key ] ){
cache[ key ] = [];
}
cache[key].push( fn );
};
_remove = function( key, cache ,fn){
if ( cache[ key ] ){
if( fn ){
for( var i = cache[ key ].length; i >= 0; i-- ){
if( cache[ key ] === fn ){
cache[ key ].splice( i, 1 );
}
}
}else{
cache[ key ] = [];
}
}
};
_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[ key ];
if ( !stack || !stack.length ){
return;
}
return each( stack, function(){
return this.apply( _self, args );
});
};
_create = function( namespace ){
var namespace = namespace || _default;
var cache = {},
offlineStack = [], // 离线事件
ret = {
listen: function( key, fn, last ){
_listen( key, fn, cache );
if ( offlineStack === null ){
return;
}
if ( last === 'last' ){
}else{
each( offlineStack, function(){
this();
});
}
offlineStack = null;
},
one: function( key, fn, last ){
_remove( key, cache );
this.listen( key, fn ,last );
},
remove: function( key, fn ){
_remove( key, cache ,fn);
},
trigger: function(){
var fn,
args,
_self = this;
_unshift.call( arguments, cache );
args = arguments;
fn = function(){
return _trigger.apply( _self, args );
};
if ( offlineStack ){
return offlineStack.push( fn );
}
return fn();
}
};
return namespace ?
( namespaceCache[ namespace ] ? namespaceCache[ namespace ] :
namespaceCache[ namespace ] = ret )
: ret;
};
return {
create: _create,
one: function( key,fn, last ){
var event = this.create( );
event.one( key,fn,last );
},
remove: function( key,fn ){
var event = this.create( );
event.remove( key,fn );
},
listen: function( key, fn, last ){
var event = this.create( );
event.listen( key, fn, last );
},
trigger: function(){
var event = this.create( );
event.trigger.apply( this, arguments );
}
};
}();
return Event;
})();
先发布后订阅
Event.trigger('click',1);
Event.listen('click',function(a){
console.log(a);//输出1
})
使用命名空间
Event.create('namespace1').listen('click',function(a){
console.log(a);//输出1
});
Event.create('namespace1').trigger('click',1);
Event.create('namespace2').listen('click',function(a){
console.log(a);//输出2
});
Event.create('namespace2').trigger('click',2);
命令模式
什么是命令模式
分别有 3 个不同的主体:调用者、传递者和执行者。
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
命令模式的特点
- 请求发送者和请求接收者之间解耦
- command对象拥有更长的生命周期
- 支持撤销、恢复
队列
案例
菜单按钮事件绑定
<button id="button1">点击按钮1</button> <button id="button2">点击按钮2</button> <button id="button3">点击按钮3</button>
var button1 = document.getElementById( 'button1' ), var button2 = document.getElementById( 'button2' ), var button3 = document.getElementById( 'button3' ); var setCommand = function( button, command ){ button.onclick = function(){ command.execute(); } }; var MenuBar = { refresh: function(){ console.log( '刷新菜单目录' ); } }; //在让button 变得有用起来之前,我们要先把这些行为都封装在命令类中: var RefreshMenuBarCommand = function( receiver ){ return { execute: function(){ receiver.refresh(); } } }; var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); setCommand( button1, refreshMenuBarCommand );
小球动画撤销
<div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div> 输入小球移动后的位置:<input id="pos"/> <button id="moveBtn">开始移动</button> <div id="ball" style="position:absolute;background:#000;width:50px;height:50px"></div> 输入小球移动后的位置:<input id="pos"/> <button id="moveBtn">开始移动</button> <button id="cancelBtn">cancel</cancel> <!--增加取消按钮-->
var ball = document.getElementById( 'ball' ); var pos = document.getElementById( 'pos' ); var moveBtn = document.getElementById( 'moveBtn' ); var cancelBtn = document.getElementById( 'cancelBtn' ); var MoveCommand = function( receiver, pos ){ this.receiver = receiver; this.pos = pos; this.oldPos = null; }; MoveCommand.prototype.execute = function(){ this.receiver.start( 'left', this.pos, 1000, 'strongEaseOut' ); this.oldPos = this.receiver.dom.getBoundingClientRect()[ this.receiver.propertyName ]; // 记录小球开始移动前的位置 }; MoveCommand.prototype.undo = function(){ this.receiver.start( 'left', this.oldPos, 1000, 'strongEaseOut' ); // 回到小球移动前记录的位置 }; var moveCommand; moveBtn.onclick = function(){ var animate = new Animate( ball ); moveCommand = new MoveCommand( animate, pos.value ); moveCommand.execute(); }; cancelBtn.onclick = function(){ moveCommand.undo(); // 撤销命令 };
街头霸王重做
<button id="replay">播放录像</button>
var Ryu = { attack: function(){ console.log( '攻击' ); }, defense: function(){ console.log( '防御' ); }, jump: function(){ console.log( '跳跃' ); }, crouch: function(){ console.log( '蹲下' ); } }; var makeCommand = function( receiver, state ){ // 创建命令 return function(){ receiver[ state ](); } }; var commands = { "119": "jump", // W "115": "crouch", // S "97": "defense", // A "100": "attack" // D }; var commandStack = []; // 保存命令的堆栈 document.onkeypress = function( ev ){ var keyCode = ev.keyCode, command = makeCommand( Ryu, commands[ keyCode ] ); if ( command ){ command(); // 执行命令 commandStack.push( command ); // 将刚刚执行过的命令保存进堆栈 } }; document.getElementById( 'replay' ).onclick = function(){ // 点击播放录像 var command; while( command = commandStack.shift() ){ // 从堆栈里依次取出命令并执行 command(); } };
宏命令
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();
组合模式
什么是组合模式?
将对象组合成树形结构,表示:“部分-整体”的层次结构,组合对象只负责传递请求给叶对象
特点
- 好处:通过对象的多态性统一对待组合对象和单个对象、
- 统一对待树中所有对象
- 组合模式是HAS-A(聚合)关系,不是IS-A
- 拥有相同接口
- 用职责链模式提高组合模式性能
案例
万能遥控器
<body>
<button id="button">按我</button>
</body>
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 openAcCommand = {
execute: function(){
console.log( '打开空调' );
}
};
/**********家里的电视和音响是连接在一起的,所以可以用一个宏命令来组合打开电视和打开音响的命令
*********/
var openTvCommand = {
execute: function(){
console.log( '打开电视' );
}
};
var openSoundCommand = {
execute: function(){
console.log( '打开音响' );
}
};
var macroCommand1 = MacroCommand();
macroCommand1.add( openTvCommand );
macroCommand1.add( openSoundCommand );
/*********关门、打开电脑和打登录QQ 的命令****************/
var closeDoorCommand = {
execute: function(){
console.log( '关门' );
}
};
var openPcCommand = {
execute: function(){
console.log( '开电脑' );
}
};
var openQQCommand = {
execute: function(){
console.log( '登录QQ' );
}
};
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 );
由于js是动态语言类型,没有编译器检查变量类型,抽象类会有安全问题,存在误操作,所以需要通过鸭子类型思想对它们进行接口检查,通过抛出异常来提醒开发者或用户
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 openTvCommand = {
execute: function(){
console.log( '打开电视' );
},
add: function(){
throw new Error( '叶对象不能添加子节点' );
}
};
var macroCommand = MacroCommand();
macroCommand.add( openTvCommand );
openTvCommand.add( macroCommand ) // Uncaught Error: 叶对象不能添加子节点
扫描文件夹
var Folder = function( name ){
this.name = name;
this.files = [];
};
Folder.prototype.add = function( file ){
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '开始扫描文件夹: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
/******************************* File ******************************/
var File = function( name ){
this.name = name;
};
File.prototype.add = function(){
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 );
var folder3 = new Folder( 'Nodejs' );
var file4 = new File( '深入浅出Node.js' );
folder3.add( file4 );
var file5 = new File( 'JavaScript 语言精髓与编程实践' );
folder.add( folder3 );
folder.add( file5 );
folder.scan();
扫描文件-添加父节点-引入职责链
var Folder = function( name ){
this.name = name;
this.parent = null; //增加this.parent 属性
this.files = [];
};
Folder.prototype.add = function( file ){
file.parent = this; //设置父对象
this.files.push( file );
};
Folder.prototype.scan = function(){
console.log( '开始扫描文件夹: ' + this.name );
for ( var i = 0, file, files = this.files; file = files[ i++ ]; ){
file.scan();
}
};
Folder.prototype.remove = function(){
if ( !this.parent ){ //根节点或者树外的游离节点
return;
}
for ( var files = this.parent.files, l = files.length - 1; l >=0; l-- ){
var file = files[ l ];
if ( file === this ){
files.splice( l, 1 );
}
}
};
var File = function( name ){
this.name = name;
this.parent = null;
};
File.prototype.add = function(){
throw new Error( '不能添加在文件下面' );
};
File.prototype.scan = function(){
console.log( '开始扫描文件: ' + this.name );
};
File.prototype.remove = function(){
if ( !this.parent ){ //根节点或者树外的游离节点
return;
}
for ( var files = this.parent.files, l = files.length - 1; l >=0; l-- ){
var file = files[ l ];
if ( file === this ){
files.splice( l, 1 );
}
}
};
var folder = new Folder( '学习资料' );
var folder1 = new Folder( 'JavaScript' );
var file1 = new Folder ( '深入浅出Node.js' );
folder1.add( new File( 'JavaScript 设计模式与开发实践' ) );
folder.add( folder1 );
folder.add( file1 );
folder1.remove(); //移除文件夹
folder.scan();
模板方法模式
什么是模板模式?
基于继承的设计模式。由两部分组成:
1、抽象父类:
抽象父类中封装了子类的算法框架,包括:公共方法和封装子类中所有方法的执行顺序
2、具体的实现子类:
继承整个算法结构,并可选的重写父类方法
特点
- 符合开放-封闭原则,通过封装变化提高系统扩展性的设计模式
- 子类方法种类和执行顺序不变,把这部分抽象到父类模板方法中
- 可以用钩子方法,实现个性部分
常用于架构师搭建项目框架
案例
Coffee or Tea
1、没引入模板前
var Coffee = function(){}; Coffee.prototype.boilWater = function(){ console.log( '把水煮沸' ); }; Coffee.prototype.brewCoffeeGriends = function(){ console.log( '用沸水冲泡咖啡' ); }; Coffee.prototype.pourInCup = function(){ console.log( '把咖啡倒进杯子' ); }; Coffee.prototype.addSugarAndMilk = function(){ console.log( '加糖和牛奶' ); }; Coffee.prototype.init = function(){ this.boilWater(); this.brewCoffeeGriends(); this.pourInCup(); this.addSugarAndMilk(); }; var coffee = new Coffee(); coffee.init(); var Tea = function(){}; Tea.prototype.boilWater = function(){ console.log( '把水煮沸' ); }; Tea.prototype.steepTeaBag = function(){ console.log( '用沸水浸泡茶叶' ); }; Tea.prototype.pourInCup = function(){ console.log( '把茶水倒进杯子' ); }; Tea.prototype.addLemon = function(){ console.log( '加柠檬' ); }; Tea.prototype.init = function(){ this.boilWater(); this.steepTeaBag(); this.pourInCup(); this.addLemon(); }; var tea = new Tea(); tea.init();
改进后
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(); //个性的子类,通过引入钩子方法hook来解决 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();
高阶函数方式实现模板方法(推荐使用这种)
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();
享元模式
什么是享元模式?
用于性能优化的模式,将对象的属性划分为内部状态和外部状态,目标是,尽量减少共享对象的数量
特点
通用结构:
1、通过工厂模式,当某个共享对象真正需要时,从工厂创建
适用性:- 一个程序使用大量相同对象
- 由于使用大量对象,造成很大内存开销
- 对象大部分状态可以变为外部状态
没有内部状态:可以用单例工厂方式创建
没有外部状态:用对象池
对象池概念:对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接new,而是从对象池里获取,如果对象池没有空闲对象,则创建新对象,当获取完它的职责后,在进入池子等待下次获取
案例
通过男女模特试穿内衣例子-理解享元模式
//创建100个对象,性能差
var Model = function( sex, underwear){
this.sex = sex;
this.underwear= underwear;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
for ( var i = 1; i <= 50; i++ ){
var maleModel = new Model( 'male', 'underwear' + i );
maleModel.takePhoto();
};
for ( var j = 1; j <= 50; j++ ){
var femaleModel= new Model( 'female', 'underwear' + j );
femaleModel.takePhoto();
};
使用享元模式,降低对象创建
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();
};
for ( var j = 1; j <= 50; j++ ){
femaleModel.underwear = 'underwear' + j;
femaleModel.takePhoto();
};
文件上传
var id = 0;
window.startUpload = function( uploadType, files ){ // uploadType 区分是控件还是flash
for ( var i = 0, file; file = files[ i++ ]; ){
var uploadObj = new Upload( uploadType, file.fileName, file.fileSize );
uploadObj.init( id++ ); // 给upload 对象设置一个唯一的id
}
};
Upload:接收三个参数:插件类型,文件名,文件大小
var Upload = function( uploadType, fileName, fileSize ){
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom= null;
};
Upload.prototype.init = function( id ){
var that = this;
this.id = id;
this.dom = document.createElement( 'div' );
this.dom.innerHTML =
'<span>文件名称:'+ this.fileName +', 文件大小: '+ this.fileSize +'</span>' +
'<button class="delFile">删除</button>';
this.dom.querySelector( '.delFile' ).onclick = function(){
that.delFile();
}
document.body.appendChild( this.dom );
};
Upload.prototype.delFile = function(){
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '确定要删除该文件吗? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
};
创建3个插件上传对象和3个flash对象
startUpload( 'plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload( 'flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
]);
使用享元模式重构文件上传
var Upload = function( uploadType){
this.uploadType = uploadType;
};
Upload.prototype.delFile = function( id ){
uploadManager.setExternalState( id, this ); // (1)
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '确定要删除该文件吗? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
}
工厂进行对象实例化
var UploadFactory = (function(){
var createdFlyWeightObjs = {};
return {
create: function( uploadType){
if ( createdFlyWeightObjs [ uploadType] ){
return createdFlyWeightObjs [ uploadType];
}
return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
}
}
})();
管理器封装外部状态
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function( id, uploadType, fileName, fileSize ){
var flyWeightObj = UploadFactory.create( uploadType );
var dom = document.createElement( 'div' );
dom.innerHTML =
'<span>文件名称:'+ fileName +', 文件大小: '+ fileSize +'</span>' +
'<button class="delFile">删除</button>';
dom.querySelector( '.delFile' ).onclick = function(){
flyWeightObj.delFile( id );
}
document.body.appendChild( dom );
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj ;
},
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[ id ];
for ( var i in uploadData ){
flyWeightObj[ i ] = uploadData[ i ];
}
}
}
})();
对象池案例-地图创建toolTip
场景第一次地图搜索:超市创建2个气泡,第二次搜索:兰州拉面,找到6个气泡,用对象池思想,第二次搜索前,不会把第一次2个气泡删除,把它们放进对象池,第二次搜索结果页面里,我们只需创建4个气泡
定义获小气泡的工厂,对外暴露两个方法,一个create 表示获取一个div节点,recover表示回收一个div节点
var toolTipFactory = (function(){
var toolTipPool = []; // toolTip 对象池
return {
create: function(){
if ( toolTipPool.length === 0 ){ // 如果对象池为空
var div = document.createElement( 'div' ); // 创建一个dom
document.body.appendChild( div );
return div;
}else{ // 如果对象池里不为空
return toolTipPool.shift(); // 则从对象池中取出一个dom
}
},
recover: function( tooltipDom ){
return toolTipPool.push( tooltipDom ); // 对象池回收dom
}
}
})();
var ary = [];
for ( var i = 0, str; str = [ 'A', 'B' ][ i++ ]; ){
var toolTip = toolTipFactory.create();
toolTip.innerHTML = str;
ary.push( toolTip );
};
for ( var i = 0, toolTip; toolTip = ary[ i++ ]; ){
toolTipFactory.recover( toolTip );
};
for ( var i = 0, str; str = [ 'A', 'B', 'C', 'D', 'E', 'F' ][ i++ ]; ){
var toolTip = toolTipFactory.create();
toolTip.innerHTML = str;
};
通用对象池实现
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 );
职责链模式
什么是职责链?
使多个对象都有机会处理请求,从而避免请求发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着链条传递请求,直到有一个对象处理它为止(例如:公交车传递硬币)
特点
- 请求发送者只要知道链条第一个节点,弱化发送者和一组接收者的强联系
- 链中的节点对象可以灵活的拆分重组
可以手动指定起始节点,请求并不是非得从链中第一个节点开始传递
案例
1、售手机电商根据定金策略发优惠券
未使用职责链代码
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 优惠券
使用职责链重构代码(灵活可拆分)
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( '手机库存不足' );
}
};
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
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 ); // 输出:手机库存不足
异步职责链
用AOP实现职责链
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 = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出:500 元定金预购,得到100 优惠券
order( 2, true, 500 ); // 输出:200 元定金预购,得到50 优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券
中介模式
什么是中介模式?
解除对象与对象之间的紧耦合关系,增加中介者对象后,所有的相关对象都通过中介对象来通信,当一个对象发生改变时,只需要通知中介者对象即可
帮助理解:现实中的机场指挥塔
特点
对象之间解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系
案例
1、泡泡堂游戏
function Player( name, teamColor ){
this.name = name; // 角色名字
this.teamColor = teamColor; // 队伍颜色
this.state = 'alive'; // 玩家生存状态
};
Player.prototype.win = function(){
console.log( this.name + ' won ' );
};
Player.prototype.lose = function(){
console.log( this.name +' lost' );
};
/*******************玩家死亡*****************/
Player.prototype.die = function(){
this.state = 'dead';
playerDirector.reciveMessage( 'playerDead', this ); // 给中介者发送消息,玩家死亡
};
/*******************移除玩家*****************/
Player.prototype.remove = function(){
playerDirector.reciveMessage( 'removePlayer', this ); // 给中介者发送消息,移除一个玩家
};
/*******************玩家换队*****************/
Player.prototype.changeTeam = function( color ){
playerDirector.reciveMessage( 'changeTeam', this, color ); // 给中介者发送消息,玩家换队
};
var playerDirector= ( function(){
var players = {}, // 保存所有玩家
operations = {}; // 中介者可以执行的操作
/****************新增一个玩家***************************/
operations.addPlayer = function( player ){
var teamColor = player.teamColor; // 玩家的队伍颜色
players[ teamColor ] = players[ teamColor ] || []; // 如果该颜色的玩家还没有成立队伍,则
players[ teamColor ].push( player ); // 添加玩家进队伍
};
/****************移除一个玩家***************************/
operations.removePlayer = function( player ){
var teamColor = player.teamColor, // 玩家的队伍颜色
teamPlayers = players[ teamColor ] || []; // 该队伍所有成员
for ( var i = teamPlayers.length - 1; i >= 0; i-- ){ // 遍历删除
if ( teamPlayers[ i ] === player ){
teamPlayers.splice( i, 1 );
}
}
};
/****************玩家换队***************************/
operations.changeTeam = function( player, newTeamColor ){ // 玩家换队
operations.removePlayer( player ); // 从原队伍中删除
player.teamColor = newTeamColor; // 改变队伍颜色
operations.addPlayer( player ); // 增加到新队伍中
};
operations.playerDead = function( player ){ // 玩家死亡
var teamColor = player.teamColor,
teamPlayers = players[ teamColor ]; // 玩家所在队伍
var all_dead = true;
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
if ( player.state !== 'dead' ){
all_dead = false;
break;
}
}
if ( all_dead === true ){ // 全部死亡
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.lose(); // 本队所有玩家lose
}
for ( var color in players ){
if ( color !== teamColor ){
var teamPlayers = players[ color ]; // 其他队伍的玩家
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.win(); // 其他队伍所有玩家win
}
}
}
}
};
var reciveMessage = function(){
var message = Array.prototype.shift.call( arguments ); // arguments 的第一个参数为消息名称
operations[ message ].apply( this, arguments );
};
return {
reciveMessage: reciveMessage
}
})();
// 红队:
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();
player3.die();
player4.die();
player1.remove();
player2.remove();
player3.die();
player4.die();
player1.changeTeam( 'blue' );
player2.die();
player3.die();
player4.die();
装饰器模式(decorator)
什么是装饰器模式?
给i对象动态增加职责的方式,能够在不改变对象自身基础上,咋程序运行期间动态添加职责
特点
装饰者对象和它所装饰的对象拥有一致的接口
也称为包装器(wrapper)
案例
1、js装饰
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();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
2、用AOP方式
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;
}
};
AOP应用:
Function.prototype.before = function( beforefn ){
var __self = this;
return function(){
beforefn.apply( this, arguments );
return __self.apply( this, arguments );
}
}
document.getElementById = document.getElementById.before(function(){
alert (1);
});
var button = document.getElementById( 'button' );
console.log( button );
window.onload = function(){
alert (1);
}
window.onload = ( window.onload || function(){} ).after(function(){
alert (2);
}).after(function(){
alert (3);
}).after(function(){
alert (4);
});
var before = function( fn, beforefn ){
return function(){
beforefn.apply( this, arguments );
return fn.apply( this, arguments );
}
}
var a = before(
function(){alert (3)},
function(){alert (2)}
);
a = before( a, function(){alert (1);} );
a();
3、数据统计上报
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};
var showLogin = function(){
console.log( '打开登录浮层' );
}
var log = function(){
console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );
}
showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据
document.getElementById( 'button' ).onclick = showLogin;
4、插件表单验证
用户名:<input id="username" type="text"/>
密码: <input id="password" type="password"/>
<input id="submitBtn" type="button" value="提交">
var username = document.getElementById( 'username' ),
password = document.getElementById( 'password' ),
submitBtn = document.getElementById( 'submitBtn' );
var formSubmit = function(){
if ( username.value === '' ){
return alert ( '用户名不能为空' );
}
if ( password.value === '' ){
return alert ( '密码不能为空' );
}
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略
}
submitBtn.onclick = function(){
formSubmit();
}
var validata = function(){
if ( username.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' );
return false;
}
}
var formSubmit = function(){
if ( validata() === false ){ // 校验未通过
return;
}
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param );
}
submitBtn.onclick = function(){
formSubmit();
}
Function.prototype.before = function( beforefn ){
var __self = this;
return function(){
if ( beforefn.apply( this, arguments ) === false ){
// beforefn 返回false 的情况直接return,不再执行后面的原函数
return;
}
return __self.apply( this, arguments );
}
}
var validata = function(){
if ( username.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( password.value === '' ){
alert ( '密码不能为空' );
return false;
}
}
var formSubmit = function(){
var param = {
username: username.value,
password: password.value
}
ajax( 'http:// xxx.com/login', param );
}
formSubmit = formSubmit.before( validata );
submitBtn.onclick = function(){
formSubmit();
}
状态模式
什么是状态模式?
关键是区分事务内部的状态,,将状态封装成独立的类,并将请求委托给当前状态对象,当对象内部状态改变时,带来不同行为变化,例如电灯,在OFF和ON两种不同状态下,按同一个按钮,得到行为反馈是截然不同的
特点
缺点:系统会因此增加不少对象、我们无法在一个地方就看出整个状态机逻辑
优点:
1、context对象可以共享一个state对象
2、有两种选择来管理state对象的销毁和创建,1、仅当state对象被需要时才创建,随后销毁,另一种一开始就创建好所有,并始终不销毁
案例
1、灯光
var Light = function(){
this.state = 'off'; // 给电灯设置初始状态off
this.button = null; // 电灯开关按钮
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
button.innerHTML = '开关';
this.button = document.body.appendChild( button );
this.button.onclick = function(){
self.buttonWasPressed();
}
};
Light.prototype.buttonWasPressed = function(){
if ( this.state === 'off' ){
console.log( '开灯' );
this.state = 'on';
}else if ( this.state === 'on' ){
console.log( '关灯' );
this.state = 'off';
}
};
var light = new Light();
light.init();
Light.prototype.buttonWasPressed = function(){
if ( this.state === 'off' ){
console.log( '弱光' );
this.state = 'weakLight';
}else if ( this.state === 'weakLight' ){
console.log( '强光' );
this.state = 'strongLight';
}else if ( this.state === 'strongLight' ){
console.log( '关灯' );
this.state = 'off';
}
};
var OffLightState = function( light ){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log( '弱光' ); // offLightState 对应的行为
this.light.setState( this.light.weakLightState ); // 切换状态到weakLightState
};
// WeakLightState:
var WeakLightState = function( light ){
this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function(){
console.log( '强光' ); // weakLightState 对应的行为
this.light.setState( this.light.strongLightState ); // 切换状态到strongLightState
};
// StrongLightState:
var StrongLightState = function( light ){
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
console.log( '关灯' ); // strongLightState 对应的行为
this.light.setState( this.light.offLightState ); // 切换状态到offLightState
};
var Light = function(){
this.offLightState = new OffLightState( this );
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置当前状态
this.button.onclick = function(){
self.currState.buttonWasPressed();
}
};
Light.prototype.setState = function( newState ){
this.currState = newState;
};
var light = new Light();
light.init();
var Light = function(){
this.offLightState = new OffLightState( this ); // 持有状态对象的引用
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.superStrongLightState = new SuperStrongLightState( this );
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置默认初始状态
this.button.onclick = function(){ // 定义用户的请求动作
self.currState.buttonWasPressed();
}
};
var OffLightState = function( light ){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log( '弱光' );
this.light.setState( this.light.weakLightState );
};
var State = function(){};
State.prototype.buttonWasPressed = function(){
throw new Error( '父类的buttonWasPressed 方法必须被重写' );
};
var SuperStrongLightState = function( light ){
this.light = light;
};
SuperStrongLightState.prototype = new State(); // 继承抽象父类
SuperStrongLightState.prototype.buttonWasPressed = function(){ // 重写buttonWasPressed 方法
console.log( '关灯' );
this.light.setState( this.light.offLightState );
};
文件上传
window.external.upload = function( state ){
console.log( state ); // 可能为sign、uploading、done、error
};
var plugin = (function(){
var plugin = document.createElement( 'embed' );
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function(){
console.log( '开始文件扫描' );
}
plugin.pause = function(){
console.log( '暂停文件上传' );
};
plugin.uploading = function(){
console.log( '开始文件上传' );
};
plugin.del = function(){
console.log( '删除文件上传' );
}
plugin.done = function(){
console.log( '文件上传完成' );
}
document.body.appendChild( plugin );
return plugin;
})();
var Upload = function( fileName ){
this.plugin = plugin;
this.fileName = fileName;
this.button1 = null;
this.button2 = null;
this.state = 'sign'; // 设置初始状态为waiting
};
Upload.prototype.init = function(){
var that = this;
this.dom = document.createElement( 'div' );
this.dom.innerHTML =
'<span>文件名称:'+ this.fileName +'</span>\
<button data-action="button1">扫描中</button>\
<button data-action="button2">删除</button>';
document.body.appendChild( this.dom );
this.button1 = this.dom.querySelector( '[data-action="button1"]' ); // 第一个按钮
this.button2 = this.dom.querySelector( '[data-action="button2"]' ); // 第二个按钮
this.bindEvent();
};
Upload.prototype.bindEvent = function(){
var self = this;
this.button1.onclick = function(){
if ( self.state === 'sign' ){ // 扫描状态下,任何操作无效
console.log( '扫描中,点击无效...' );
}else if ( self.state === 'uploading' ){ // 上传中,点击切换到暂停
self.changeState( 'pause' );
}else if ( self.state === 'pause' ){ // 暂停中,点击切换到上传中
self.changeState( 'uploading' );
}else if ( self.state === 'done' ){
console.log( '文件已完成上传, 点击无效' );
}else if ( self.state === 'error' ){
console.log( '文件上传失败, 点击无效' );
}
};
this.button2.onclick = function(){
if ( self.state === 'done' || self.state === 'error' || self.state === 'pause' ){
// 上传完成、上传失败和暂停状态下可以删除
self.changeState( 'del' );
}else if ( self.state === 'sign' ){
console.log( '文件正在扫描中,不能删除' );
}else if ( self.state === 'uploading' ){
console.log( '文件正在上传中,不能删除' );
}
};
};
Upload.prototype.changeState = function( state ){
switch( state ){
case 'sign':
this.plugin.sign();
this.button1.innerHTML = '扫描中,任何操作无效';
break;
case 'uploading':
this.plugin.uploading();
this.button1.innerHTML = '正在上传,点击暂停';
break;
case 'pause':
this.plugin.pause();
this.button1.innerHTML = '已暂停,点击继续上传';
break;
case 'done':
this.plugin.done();
this.button1.innerHTML = '上传完成';
break;
case 'error':
this.button1.innerHTML = '上传失败';
break;
case 'del':
this.plugin.del();
this.dom.parentNode.removeChild( this.dom );
console.log( '删除完成' );
break;
}
this.state = state;
};
var uploadObj = new Upload( 'JavaScript 设计模式与开发实践' );
uploadObj.init();
window.external.upload = function( state ){ // 插件调用JavaScript 的方法
uploadObj.changeState( state );
};
window.external.upload( 'sign' ); // 文件开始扫描
setTimeout(function(){
window.external.upload( 'uploading' ); // 1 秒后开始上传
}, 1000 );
setTimeout(function(){
window.external.upload( 'done' ); // 5 秒后上传完成
}, 5000 );
适配器模式
什么是适配器模式?
解决两个软件实体间的接口不兼容问题,现实中适配器:usb转接口
特点
案例
1、地图渲染
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMapAdapter ); // 输出:开始渲染百度地图
var getGuangdongCity = function(){
var guangdongCity = [
{
name: 'shenzhen',
id: 11,
}, {
name: 'guangzhou',
id: 12,
}
];
return guangdongCity;
};
var render = function( fn ){
console.log( '开始渲染广东省地图' );
document.write( JSON.stringify( fn() ) );
};
render( getGuangdongCity );
var guangdongCity = {
shenzhen: 11,
guangzhou: 12,
zhuhai: 13
};
var getGuangdongCity = function(){
var guangdongCity = [
{
name: 'shenzhen',
id: 11,
}, {
name: 'guangzhou',
id: 12,
}
];
return guangdongCity;
};
var render = function( fn ){
console.log( '开始渲染广东省地图' );
document.write( JSON.stringify( fn() ) );
};
var addressAdapter = function( oldAddressfn ){
var address = {},
oldAddress = oldAddressfn();
for ( var i = 0, c; c = oldAddress[ i++ ]; ){
address[ c.name ] = c.id;
}
return function(){
return address;
}
};
render( addressAdapter( getGuangdongCity ) );
设计原则和编程技巧
添加当前模块大概功能的描述,希望不要把所有接口文档写在一个文件中,至少按模块分类。
包括标题也应该改成模块名称
查看直接看右边大纲
每个接口以分割线为界
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。