设计模式并非是软件开发的专业术语。实际上,“模式”最早诞生于建筑学。20世纪70年代,哈佛大学建筑学博士Christopher Alexander和他的研究团队花了约20年的时间,研究了为解决同一个问题而设计出的不同建筑结构,从中发现了那些高质量设计中的相似性,并且用“模式”来指 代这种相似性。
受Christopher Alexander工作的启发,Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides四人(人称Gang Of Four ,GoF)把这种“模式”观点应用于面向对象的软件设计中,并且总结了23种常见的软件开发设计模式,录入《设计模式:可复用面向对象软件的基础》一书。
设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
// 创建对象和管理单例的职责被分布在两个不同的方法中(符合单一职责原则),这两个方法组合起来才具有单例模式的威力。
const getSingle = function( fn ){
const result;
return function(){
return result || ( result = fn.apply(this, arguments ) );
}
};
const createLoginLayer = function(){
const div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
};
const createSingleLoginLayer = getSingle( createLoginLayer );
document.getElementById( 'loginBtn' ).onclick = function(){
const loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
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
一个文本输入框对应多种校验规则:
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/ >
请输入密码:<input type="text" name="password"/ >
请输入手机号码:<input type="text" name="phoneNumber"/ >
<button>提交</button>
</form>
<script>
/***********************策略对象**************************/
var strategies = {
isNonEmpty: function( value, errorMsg ){
if ( value === '' ){
return errorMsg;
}
},
minLength: function( value, length, errorMsg ){
if ( value.length < length ){
return errorMsg;
}
},
isMobile: function( value, errorMsg ){
if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
return errorMsg;
}
}
};
/***********************Validator 类**************************/
var Validator = function(){
this.cache = [];
};
Validator.prototype.add = function( dom, rules ){
var self = this;
for ( var i = 0, rule; rule = rules[ i++ ]; ){
(function( rule ){
var strategyAry = rule.strategy.split( ':' );
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg );
return strategies[ strategy ].apply( dom, strategyAry );
});
})( rule )
}
};
Validator.prototype.start = function(){
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var errorMsg = validatorFunc();
if ( errorMsg ){
return errorMsg;
}
}
};
/***********************客户调用代码**************************/
var registerForm = document.getElementById( 'registerForm' );
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于10 位'
}]);
validator.add( registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于6 位'
}]);
validator.add( registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
};
</script>
</body>
</html>
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' );
虚拟代理合并HTTP 请求:通过一个代理函数proxySynchronousFile 来收集一段时间之内的请求,最后一次性发送给服务器
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [] // 保存一段时间内需要同步的ID
var 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 );
}
}
};
var compare = function(ary1, ary2) {
if (ary1.length !== ary2.length) {
throw new Error ( 'ary1 和ary2 不相等' );
}
each(ary1, function(i, n) {
if (n !== ary2[ i ]) {
throw new Error ( 'ary1 和ary2 不相等' );
}
});
alert ( 'ary1 和ary2 相等' );
};
compare( [ 1, 2, 3 ], [ 1, 2, 4 ] ); // throw new Error ( 'ary1 和ary2 不相等' );
var Iterator = function( obj ){
var current = 0;
var next = function(){
current += 1;
};
var isDone = function(){
return current >= obj.length;
};
var getCurrItem = function(){
return obj[ current ];
};
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem
}
};
var compare = function( iterator1, iterator2 ){
while( !iterator1.isDone() && !iterator2.isDone() ){
if ( iterator1.getCurrItem() !== iterator2.getCurrItem() ){
throw new Error ( 'iterator1 和iterator2 不相等' );
}
iterator1.next();
iterator2.next();
}
alert ( 'iterator1 和iterator2 相等' );
}
var iterator1 = Iterator( [ 1, 2, 3 ] );
var iterator2 = Iterator( [ 1, 2, 3 ] );
compare( iterator1, iterator2 ); // 输出:iterator1 和iterator2 相等
在 JavaScript 中,for in 语句可以用来迭代普通字面量对象的属性
// 把发布—订阅的功能提取出来,放在一个单独的对象内
var event = {
clientList: [],
listen: function( key, fn ){
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn ); // 订阅的消息添加进缓存列表
},
trigger: function(){
var key = Array.prototype.shift.call( arguments ) // (1);
var fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是trigger 时带上的参数
}
},
remove: function( key, fn ){
var fns = this.clientList[ key ];
if ( !fns ){ // 如果key 对应的消息没有被人订阅,则直接返回
return false;
}
if ( !fn ){ // 如果没有传入具体的回调函数,表示需要取消key 对应消息的所有订阅
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 ); // 删除订阅者的回调函数
}
}
}
};
};
// 再定义一个installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
// 再来测试一番,我们给售楼处对象salesOffices 动态增加发布—订阅功能:
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){ // 小明订阅消息
console.log( '价格= ' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){ // 小红订阅消息
console.log( '价格= ' + price );
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000
命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品。
##
建造者模式(Builder Pattern):用来创建复杂的复合对象
总原则——开闭原则(Open Closed Principle): 一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。 在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。 想要达到这样的效果,我们需要使用接口和抽象类等。