
这个生成灰色背景遮罩层的代码是很好写的.
var createMask = function(){return document,body.appendChild( document.createElement(div) );}$( "button" ).click( function(){Var mask = createMask();mask.show();})问题是, 这个遮罩层是全局唯一的, 那么每次调用createMask都会创建一个新的div, 虽然可以在隐藏遮罩层的把它remove掉. 但显然这样做不合理.var mask = document.body.appendChild( document.createElement( ""div" ) );$( ""button" ).click( function(){mask.show();} )这样确实在页面只会创建一个遮罩层div, 但是另外一个问题随之而来, 也许我们永远都不需要这个遮罩层, 那又浪费掉一个div, 对dom节点的任何操作都应该非常吝啬.var mask;var createMask = function(){if ( mask ) return mask;else{mask = document,body.appendChild( document.createElement(div) );return mask;}}看起来不错, 到这里的确完成了一个产生单列对象的函数. 我们再仔细看这段代码有什么不妥.var createMask = function(){var mask;return function(){return mask || ( mask = document.body.appendChild( document.createElement("div") ) )}}()用了个简单的闭包把变量mask包起来, 至少对于createMask函数来讲, 它是封闭的.var singleton = function( fn ){var result;return function(){return result || ( result = fn .apply( this, arguments ) );}}var createMask = singleton( function(){return document.body.appendChild( document.createElement("div") );})用一个变量来保存第一次的返回值, 如果它已经被赋值过, 那么在以后的调用中优先返回该变量. 而真正创建遮罩层的代码是通过回调函数的方式传人到singleton包装器中的. 这种方式其实叫桥接模式. 关于桥接模式, 放在后面一点点来说.var request1 = Request("cgi.xx.com/xxx" , ""get" );request1.start();request1.done( fn );var request2 = Request("cgi.xx.com/xxx" , ""jsonp" );request2.start();request2.done( fn );Request实际上就是一个工厂方法, 至于到底是产生xhr的实例, 还是jsonp的实例. 是由后来的代码决定的。
function A( name ){this.name = name;}function ObjectFactory(){var obj = {},Constructor = Array.prototype.shift.call( arguments );obj.__proto__ = typeof Constructor .prototype === "number" ? Object.prototype: Constructor .prototype;var ret = Constructor.apply( obj, arguments );return typeof ret === "object" ? ret : obj;}var a = ObjectFactory( A, "svenzeng" );alert ( a.name ); //svenzeng这段代码来自es5的new和构造器的相关说明, 可以看到,所谓的new, 本身只是一个对象的复制和改写过程, 而具体会生成什么是由调用ObjectFactory时传进去的参数所决定的。div.onclick = function click (){alert ( ""click" )}只要订阅了div的click事件. 当点击div的时候, function click就会被触发.loadImage( imgAry, function(){Map.init();Gamer.init();} )当图片加载好之后, 再渲染地图, 执行游戏逻辑. 嗯, 这个程序运行良好. 突然有一天, 我想起应该给游戏加上声音功能. 我应该让图片加载器添上一行代码.loadImage( imgAry, function(){Map.init();Gamer.init();Sount.init();} )可是写这个模块的同事A去了外地旅游. 于是我打电话给他, 喂. 你的loadImage函数在哪, 我能不能改一下, 改了之后有没有副作用. 如你所想, 各种不淡定的事发生了. 如果当初我们能这样写呢:loadImage.listen( ""ready", function(){Map.init();})loadImage.listen( ""ready", function(){Gamer.init();})loadImage.listen( ""ready", function(){Sount.init();})loadImage完成之后, 它根本不关心将来会发生什么, 因为它的工作已经完成了. 接下来它只要发布一个信号.loadImage.trigger( ”ready" );那么监听了loadImage的"ready"事件的对象都会收到通知. 就像上个面试的例子. 面试官根本不关心面试者们收到面试结果后会去哪吃饭. 他只负责把面试者的简历搜集到一起. 当面试结果出来时照着简历上的电话挨个通知.
Events = function() {var listen, log, obj, one, remove, trigger, __this;obj = {};__this = this;listen = function( key, eventfn ) { //把简历扔盒子, key就是联系方式.var stack, _ref; //stack是盒子stack = ( _ref = obj[key] ) != null ? _ref : obj[ key ] = [];return stack.push( eventfn );};one = function( key, eventfn ) {remove( key );return listen( key, eventfn );};remove = function( key ) {var _ref;return ( _ref = obj[key] ) != null ? _ref.length = 0 : void 0;};trigger = function() { //面试官打电话通知面试者var fn, stack, _i, _len, _ref, key;key = Array.prototype.shift.call( arguments );stack = ( _ref = obj[ key ] ) != null ? _ref : obj[ key ] = [];for ( _i = 0, _len = stack.length; _i < _len; _i++ ) {fn = stack[ _i ];if ( fn.apply( __this, arguments ) === false) {return false;}}return {listen: listen,one: one,remove: remove,trigger: trigger}}最后用观察者模式来做一个成人电视台的小应用.//订阅者var adultTv = Event();adultTv .listen( ""play", function( data ){alert ( "今天是谁的电影" + data.name );});//发布者adultTv .trigger( ""play", { "name": "麻生希" } )四 适配器模式var category = {music: {id: 1,children: [ , , , , ]}}dev.qplus.com里大概有4,5个页面都调用这个category对象. 春节前我休了1个星期假. 过年来之后发现邮箱里有封邮件, 设计数据库的同学把category..js也重构了一份, 并且其他几个项目里都是用了这份category.js, 我拿过来一看就傻眼了, 和我之前定的数据结构完全不一样.my.category = adapterCategory ( afu.category );适配器模式的作用很像一个转接口. 本来iphone的充电器是不能直接插在电脑机箱上的, 而通过一个usb转接口就可以了.
$id = function( id ){return jQuery( "#" + id )[0];}五 代理模式 
游戏中隆需要接受键盘的事件, 来完成相应动作.
于是我写了一个keyManage类. 其中在游戏主线程里监听keyManage的变化.
var keyMgr = keyManage();keyMgr.listen( ""change", function( keyCode ){console.log( keyCode );});图片里面隆正在放升龙拳, 升龙拳的操作是前下前+拳. 但是这个keyManage类只要发生键盘事件就会触发之前监听的change函数. 这意味着永远只能取得前,后,前,拳这样单独的按键事件,而无法得到一个按键组合。var keyMgr = keyManage();keyMgr.listen( ""change", proxy( function( keyCode ){console.log( keyCode ); //前下前+拳)} );至于proxy里面怎么实现,完全可以自由发挥。var request = Ajax.get( "cgi.xx.com/xxx" );request.send();request.done(function(){});六 桥接模式var singleton = function( fn ){var result;return function(){return result || ( result = fn .apply( this, arguments ) );}}var createMask = singleton( function(){return document.body.appendChild( document.createElement("div") );})singleton是抽象部分, 而createMask是实现部分。 他们完全可以独自变化互不影响。 如果需要再写一个单例的createScript就一点也不费力.var createScript = singleton( function(){return document.body.appendChild( document.createElement("script") );})另外一个常见的例子就是forEach函数的实现, 用来迭代一个数组.forEach = function( ary, fn ){for ( var i = 0, l = ary.length; i < l; i++ ){var c = ary[ i ];if ( fn.call( c, i, c ) === false ){return false;}}}可以看到, forEach函数并不关心fn里面的具体实现. fn里面的逻辑也不会被forEach函数的改写影响.forEach( [1,2,3], function( i, n ){alert ( n*2 )} )forEach( [1,2,3], function( i, n ){alert ( n*3 )} )七 外观模式var getName = function(){return ""svenzeng"}var getSex = function(){return "man"}如果你需要分别调用getName和getSex函数. 那可以用一个更高层的接口getUserInfo来调用.var getUserInfo = function(){var info = a() + b();return info;}也许你会问为什么一开始不把getName和getSex的代码写到一起, 比如这样var getNameAndSex = function(){return "svenzeng" + "man";}答案是显而易见的,饭堂的炒菜师傅不会因为你预定了一份烧鸭和一份白菜就把这两样菜炒在一个锅里。他更愿意给你提供一个烧鸭饭套餐。同样在程序设计中,我们需要保证函数或者对象尽可能的处在一个合理粒度,毕竟不是每个人喜欢吃烧鸭的同时又刚好喜欢吃白菜。var stopEvent = function( e ){ //同时阻止事件默认行为和冒泡e.stopPropagation();e.preventDefault();}八 访问者模式function ArrayPush() { var n = TO_UINT32( this.length );var m = %_ArgumentsLength(); for (var i = 0; i < m; i++) { this[i+n] = %_Arguments(i); //属性拷贝 } this.length = n + m; //修正length return this.length;}可以看到,ArrayPush方法没有对this的类型做任何显示的限制,所以理论上任何对象都可以被传入ArrayPush这个访问者。var Visitor = {}Visitor .push = function(){return Array.prototype.push.apply( this, arguments );}var obj = {};obj.push = Visitor .push;obj.push( ""first" );alert ( obj[0] ) //"first"alert ( obj.length ); //1九 策略模式$( div ).animate( {"left: 200px"}, 1000, "linear" ); //匀速运动$( div ).animate( {"left: 200px"}, 1000, "cubic" ); //三次方的缓动这2句代码都是让div在1000ms内往右移动200个像素. linear(匀速)和cubic(三次方缓动)就是一种策略模式的封装. 
比如姓名框里面, 需要验证非空,敏感词,字符过长这几种情况。 当然是可以写3个if else来解决,不过这样写代码的扩展性和维护性可想而知。如果表单里面的元素多一点,需要校验的情况多一点,加起来写上百个if else也不是没有可能。
所以更好的做法是把每种验证规则都用策略模式单独的封装起来。需要哪种验证的时候只需要提供这个策略的名字。就像这样:
nameInput.addValidata({notNull: true,dirtyWords: true,maxLength: 30})而notNull,maxLength等方法只需要统一的返回true或者false,来表示是否通过了验证。validataList = {notNull: function( value ){return value !== "";},maxLength: function( value, maxLen ){return value.length() > maxLen;}}可以看到,各种验证规则很容易被修改和相互替换。如果某天产品经理建议字符过长的限制改成60个字符。那只需要0.5秒完成这次工作。var Life = function(){}Life.prototype.init = function(){this.DNA复制();this.出生();this.成长();this.衰老();this.死亡();}this.prototype.DNA复制 = function(){&*$%&^%^&(&(&(&&(^^(*) //看不懂的代码}Life.prototype.出生 = function(){}Life.prototype.成长 = function(){}Life.prototype.衰老 = function(){}Life.prototype.死亡 = function(){}其中DNA复制是预先定义的算法中不变部分. 所有子类都不能改写它. 如果需要我们可以写成protected的类型.var Mammal = function(){}Mammal.prototype = Life.prototype; //继承Life然后重写出生和衰老这两个钩子函数.Mammal.prototope.出生 = function(){"胎生()}Mammal.prototype.成长 = function(){//再留给子类去实现}Mammal.prototope.衰老 = function(){自由基的过氧化反应()}Life.prototype.死亡 = function(){//再留给子类去实现}//再实现一个Dog类var = Dog = function(){}//Dog继承自哺乳动物.Dog.prototype = Mammal.prototype;var dog = new Dog();dog.init();至此,一只小狗的生命会依次经历DNA复制,出生,成长,衰老,死亡这几个过程。这些步骤早在它出生前就决定了。所幸的是,上帝没有安排好它生命的所有细节。它还是能通过对成长函数的重写,来成为一只与众不同的小狗。var gameCenter = function(){}gameCenter.ptototype.init = function(){this.login();this.gameStart();this.end();}gameCenter.prototype.login= function(){//do something}gameCenter.prototype.gameStart= function(){//空函数, 留给子类去重写}gameCenter.prototype.end= function(){alert ( "欢迎下次再来玩" );}接下来创建一个斗地主的新游戏, 只需要继承gameCenter然后重写它的gameStart函数.var 斗地主 = function(){}斗地主.prototype = gameCenter.prototype; //继承斗地主.prototype.gameStart = function(){//do something}(new 斗地主).init();这样一局新的游戏就开始了.
中介者模式

代理模式中A必然是知道B的一切,而中介者模式中A,B,C对E,F,G的实现并不关心.而且中介者模式可以连接任意多种对象。
切回到程序世界里的mvc,无论是j2ee中struts的Action. 还是js中backbone.js和spine.js里的Controler. 都起到了一个中介者的作用.
拿backbone举例. 一个mode里的数据并不确定最后被哪些view使用. view需要的数据也可以来自任意一个mode. 所有的绑定关系都是在controler里决定. 中介者把复杂的多对多关系, 变成了2个相对简单的1对多关系.

一段简单的示例代码:
var mode1 = Mode.create(), mode2 = Mode.create();var view1 = View.create(), view2 = View.create();var controler1 = Controler.create( mode1, view1, function(){view1.el.find( ""div" ).bind( ""click", function(){this.innerHTML = mode1.find( "data" );} )})var controler2 = Controler.create( mode2 view2, function(){view1.el.find( ""div" ).bind( ""click", function(){this.innerHTML = mode2.find( "data" );} )})十二 迭代器模式forEach = function( ary, fn ){ for ( var i = 0, l = ary.length; i < l; i++ ){ var c = ary[ i ]; if ( fn.call( c, i , c ) === false ){ return false; } }}forEach( [ 1, 2, 3 ], function( i, n ){alert ( i );})obejct的迭代器:forEach = function( obj, fn ){ for ( var i in obj ){ var c = obj[ i ]; if ( fn.call( c, i, c ) === false ){ return false; } }}forEach( {"a": 1,"b": 2}, function( i, n ){alert ( i );})十三 组合模式<div><span></span><span></span></div>我们想取消所有节点上绑定的事件, 需要这样写
var allNodes = document.getElementsByTagName("*");var len = allNodes.length;while( len-- ){allNodes.unbind("*");}但既然用了jquery,就肯定不会再做这么搓的事情。我们只需要$( ‘body" ).unbind( ‘*" );
注意下面那个修改资料的按钮,如果有任意一个field的验证没有通过,修改资料的按钮都将是灰色不可点的状态。 这意味着我们重新填写了表单内容后, 都得去校验每个field, 保证它们全部OK.
这代码不难实现.
if ( nameField.validata() && idCard.validata() && email.validata() && phone.validata() ){alert ( "验证OK" );}似乎我们用一个外观模式也能勉强解决这里条件分支堆砌的问题,但真正的问题是,我们并不能保证表单里field的数量,也许明天产品经理就让你删掉一个或者增加两个.那么这样的维护方式显然不能被接受.form.validata = function(){forEach( fields, function( index, field ){if ( field.validata() === false ){return false;}})return true;}十四 备忘录模式var Page = function(){var page = 1,cache = {},data;return function( page ){if ( cache[ page ] ){data = cache[ page ];render( data );}else{Ajax.send( "cgi.xx.com/xxx", function( data ){cache[ page ] = data;render( data );})}}}()十五 职责链模式
如果有1000个QQ好友, 意味着如果从头拉到尾, 会创建1000个div, 这时候有些浏览器也许已经假死了. 这还只是一个随便翻翻好友列表的操作.
所以我们想到了一种解决办法, 当滚动条滚动的时候, 把已经消失在视线外的div都删除掉. 这样页面可以保持只有一定数量的节点. 问题是这样频繁的添加与删除节点, 也会造成很大的性能开销, 而且这种感觉很不对味.
现在享元模式可以登场了. 顾名思义, 享元模式可以提供一些共享的对象以便重复利用. 仔细看下上图, 其实我们一共只需要10个div来显示好友信息,也就是出现在用户视线中的10个div.这10个div就可以写成享元.
伪代码如下.
var getDiv = (function(){var created = [];var create = function(){return document.body.appendChild( document.createElement( "div" ) );}var get = function(){if ( created.length ){return created.shift();}else{return create();}}/* 一个假设的事件,用来监听刚消失在视线外的div,实际上可以通过监听滚 动条位置来实现 */userInfoContainer.disappear(function( div ){created.push( div );})})()var div = getDiv();div.innerHTML = "${userinfo}";原理其实很简单, 把刚隐藏起来的div放到一个数组中, 当需要div的时候, 先从该数组中取, 如果数组中已经没有了, 再重新创建一个. 这个数组里的div就是享元, 它们每一个都可以当作任何用户信息的载体.
隆有走动,攻击,防御,跌倒,跳跃等等多种状态,而这些状态之间既有联系又互相约束。比如跳跃的时候是不能攻击和防御的。跌倒的时候既不能攻击又不能防御,而走动的时候既可以攻击也可以跳跃。
要完成这样一系列逻辑, 常理下if else是少不了的. 而且数量无法估计, 特别是增加一种新状态的时候, 可能要从代码的第10行一直改到900行.
if ( state === "jump" ){if ( currState === "attack" || currState === "defense" ){return false;}}else if ( state === "wait" ){if ( currState === "attack" || currState === "defense" ){return true;}}为了消灭这些if else, 并且方便修改和维护, 我们引入一个状态类.var StateManager = function(){var currState = "wait";var states = {jump: function( state ){},wait: function( state ){},attack: function( state ){},crouch: function( state ){},defense: function( state ){if ( currState === "jump" ){return false; //不成功,跳跃的时候不能防御}//do something; //防御的真正逻辑代码, 为了防止状态类的代码过多, 应该把这些逻辑继续扔给真正的fight类来执行.currState = "defense"; // 切换状态}}var changeState = function( state ){states[ state ] && states[ state ]();}return {changeState : changeState}}var stateManager = StateManager();stateManager.changeState( "defense" );通过这个状态类,可以把散落在世界各地的条件分支集中管理到一个类里,并且可以很容易的添加一种新的状态。而作为调用者,只需要通过暴露的changeState接口来切换人物的状态。