乐趣区

关于javascript:js设计模式与开发实践学习笔记

基础知识

设计模式

单例模式

什么是单例?

保障一个类只能创立一个实例,并且提供拜访他的全局拜访点
例如:登录按钮,点击创立一次登录浮框,再点击不会创立,会返回方才创立的浮框。常见的利用场景:线程池、全局缓存、浏览器 window 对象

惰性单例特点

  1. 在须要的时候才创立
  2. 把治理单例和创建对象分为两个办法来实现
  3. 恪守繁多责任准则

    惰性单例案例:

//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 承受用户信息,而后将申请委托给策略类
(现实生活中,咱们要去一个城市,交通形式就有:飞机、高铁、开车、大巴等,这些形式都能达到目的地,咱们能够依据本身需要来抉择一个策略)

策略模式特点

长处
  1. 策略模式利用组合、委托和多态等技术思维,能够无效的防止许多重条件抉择语句
  2. 有弹性,恪守关闭 / 开发准则,将算法封装在独立的 strategy 中,使他易于切换、易于了解、易于扩大
  3. 复用不便
  4. 利用委托和组合来让 context 领有执行算法的能力,这也是继承的一种更轻便的代替计划

    毛病
  5. 会产生比拟多的类和函数
  6. 要应用策略就要明确所有策略,违反常识起码化准则

    案例

    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 个不同的主体:调用者、传递者和执行者。
申请以命令的模式包裹在对象中,并传给调用对象。调用对象寻找能够解决该命令的适合的对象,并把该命令传给相应的对象,该对象执行命令。

命令模式的特点

  1. 申请发送者和申请接收者之间解耦
  2. command 对象领有更长的生命周期
  3. 反对撤销、复原
  4. 队列

    案例

    菜单按钮事件绑定
     <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();

    组合模式

    什么是组合模式?

    将对象组合成树形构造,示意:“局部 - 整体”的层次结构,组合对象只负责传递申请给叶对象

    特点

  5. 益处:通过对象的多态性对立看待组合对象和单个对象、
  6. 对立看待树中所有对象
  7. 组合模式是 HAS-A(聚合)关系,不是 IS-A
  8. 领有雷同接口
  9. 用职责链模式进步组合模式性能

案例

万能遥控器
<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、具体的实现子类:
继承整个算法构造,并可选的重写父类办法

特点

  1. 合乎凋谢 - 关闭准则,通过封装变动进步零碎扩展性的设计模式
  2. 子类办法品种和执行程序不变,把这部分形象到父类模板办法中
  3. 能够用钩子办法,实现共性局部
  4. 罕用于架构师搭建我的项目框架

    案例

    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、通过工厂模式,当某个共享对象真正须要时,从工厂创立
    适用性:

  5. 一个程序应用大量雷同对象
  6. 因为应用大量对象,造成很大内存开销
  7. 对象大部分状态能够变为内部状态

没有外部状态:能够用单例工厂形式创立
没有内部状态:用对象池
对象池概念:对象池保护一个装载闲暇对象的池子,如果须要对象的时候,不是间接 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. 申请发送者只有晓得链条第一个节点,弱化发送者和一组接收者的强分割
  2. 链中的节点对象能够灵便的拆分重组
  3. 能够手动指定起始节点,申请并不是非得从链中第一个节点开始传递

    案例

    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) );

设计准则和编程技巧

增加以后模块大略性能的形容,心愿不要把所有接口文档写在一个文件中,至多按模块分类。
包含题目也应该改成模块名称
查看间接看左边纲要
每个接口以分割线为界


退出移动版