js設計模式筆記

單例模式

  • 適用場景:可能會在場景中使用到對象换可,但只有一個實例帆疟,加載時并不主動創(chuàng)建轻要,需要時才創(chuàng)建
  • 最常見的單例模式,把業(yè)務邏輯和判斷耦合在一起旱爆,如果業(yè)務邏輯變化不大的話使用
  • 以登錄組件框為例:

var createLoginLayer = (function (){}
var single = null;
return function(){
if(single){
return single;
}
var single = document.getElementById('div');
single.innerHTML = 'login';
document.body.appendChild(single);
return single;
})()
// 使用:var layer = createLoginLayer();

 - 將業(yè)務邏輯和單例判斷分離霹粥,只要變換傳入函數(shù)即可改變業(yè)務邏輯
 - ```js
  var getInstance = function(fn){
      var result = null;
      return result || (result = fn.apply(this, arguments));
  }
  function createLoginLayer(){
      var div = document.getElementById('div');
      div.innerHTML = 'login';
      document.body.appendChild(div);
      return div;
  } 
//使用 var layer = getInstance(createLoginLayer)

策略模式

  • 策略模式可以消除代碼中大片的條件分支語句

  • 案例代碼:計算獎金

  •   var calculateBonus = function(level, salary) {
      if (level === 'S') {
          return salary * 4;
      } else if(level === 'A') {
          return salary * 3;
      } else if (level === 'B') {
          return salary * 2;
      };
    

}
calculateBonus('S', 11500)

- 問題:如果要改變獎金倍數(shù)等要深入函數(shù)內部修改
- 組合函數(shù)重構代碼:

- ``` js
var calculateBonus = function(level, salary) {
  if (level === 'S') {
      levelS();
  } else if(level === 'A') {
      levelA();
  } else if (level === 'B') {
      levelB();
  };
}
function levelS() {
  return salary * 4;
}
function levelA() {
  return salary * 3;
}
function levelB() {
  return salary * 2;
}
  • 組合函數(shù)的問題在于擴展獎金等級時還要改變calculateBonus函數(shù)

  • 使用策略模式重構代碼

  • 模仿傳統(tǒng)面向對象語言中的實現(xiàn),把每種績效的計算規(guī)則都封裝在對應的策略類里面

function Bonus() {
this.strategy = null;
this.salary = null;
}
Bonus.prototype.setStrategy = function(strategy) {
this.strategy = strategy;
}
Bonus.prototype.setSalary = function(salary) {
this.salary = salary;
}
Bonus.prototype.getBonus = function() {
return this.strategy.calculate(this.salary);
}
function levelS() {
}
levelS.prototype.calculate = function(salary) {
return salary * 4;
}
function levelA() {
}
levelA.prototype.calculate = function(salary) {
return salary * 3;
}
function levelB() {
}
levelB.prototype.calculate = function(salary) {
return salary * 2;
}
// 使用
var bonus = new Bonus();
bonus.getStrategy(new levelB());


- js 版本策略模式
- ```js
var strategies = {
}
strategies.S = function(salary) {
    return salary * 4;
}
strategies.A = function(salary) {
    return salary * 3;
}
strategies.B = function(salary) {
    return salary * 2;
}
var calculateBonus = function(level, salary) {
    return strategies[level](salary);
}
calculateBonus('S', 11500);
  • 策略模式的運用 -- 緩動動畫
    • 緩動動畫是通過連續(xù)改變元素的某個css屬性疼鸟,left top等后控,這里會出現(xiàn)多個條件分支判斷,所以可使用策略模式空镜,緩動動畫有多個運動形式浩淘,也會出現(xiàn)多個分支判斷

//動畫函數(shù)庫
var teen = {
linear: function(t, b, c, d) {
return (t / d) * c + b;
},
easeIn: function(t, b, c, d) {
return (t /= d ) * c * t + b;
},
strongEaseIn: function(t, b, c, d) {
return c * (t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d) {
return c * ( (t = t / d - 1) * t * t * t * t + 1)+ b;
},
sineaseIn: function(t, b, c, d) {
return c * (t /= d ) * t * t + b;
},
sineaseOut: function(t, b, c, d) {
return c * ( (t = t / d - 1) * t * t + 1) + b;
}
}
var Animation = function(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
}
Animation.prototype.start = function( propertyName, endPos, duration, easing) {
this.startTime = +new Date;
this.startPos = this.dom.getBoundingClientRect()[propertyName];
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
this.easing = teen[easing];

var timerId = setInterval(function() {
    if (this.step() === false) {
        clearInterval(timerId);
    };
}.bind(this), 19)

}
//負責計算函數(shù)位置和調用更新css屬性值的方法
Animation.prototype.step = function() {
var t = +new Date;
if (t >= this.startTime + this.duration) {
this.update( this.endPos );
return false;
};

var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);

this.update(pos);

}
Animation.prototype.update = function(pos) {
this.dom.style[this.propertyName] = pos + 'px';
}

- ```html
<div id='div' style="width:100px; height:100px; background:red;"></div>
  <script src="animation.js"></script>
  <script>
      var dom = document.getElementById('div');
      var animation = new Animation(dom);
      animation.start('width', 500, 3000, 'sineaseOut');
  </script>
  • 利用策略模式重構表單校驗代碼
  • 案例代碼:
  • var registerForm = document.getElementBy('registerForm');
    

registerForm.onsubmit = function() {
if ( registerForm.userName.value === '' ) {
alert('用戶名不能為空');
return false;
};

if ( registerForm.password.value.length === '' ) {
    alert('密碼長度不能少于6位');
    return false;
};

if (!/(^1[3|5|8][0-9]{9}$)/).test(registerForm.phoneNumber.value)) {
    alert('手機號碼不正確');
    return false;
};

}

- 問題: 多個if else, 函數(shù)缺乏擴展性吴攒,復用性差
- 用策略模式重構表單提交代碼
- ```js
var strategies = {
    isNonEmpty: function( value, errMsg) {
        if (value === '') {
            return errMsg
        };
    },
    minLength: function(value, length, errMsg) {
        if (value.length < length) {
            return errMsg
        };
    },
    isMobile: function(value, errMsg) {
        if(!(/^1[3|5|8][0-9]{9}$/.test(value)) ) {
            return errMsg;
        }
    }
}
var Validation = function() {
    this.cache = [];
}
Validation.prototype.start = function() {
    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
        var errMsg = validatorFunc;();
        if (errMsg) {
            return errMsg;
        };
    }
}
Validation.prototype.add = function(dom, rules) {
    var self = this;
    for(var i = 0, rule; rule = rules[i++];) {
        var strategyAry = rule.strategy.split(':');
        var errMsg = rule.errMsg;

        this.cache.push(function(){
            var strategy = strategyAry.shift();
            strategyAry.unshift(dom.value);
            strategyAry.push(errMsg);
            return strategies[strategy].apply(dom, strategyAry);
        });
    }
}

<form action="#" id="registerForm">
<input type="text" name="userName">
<input type="submit">
</form>
<script src="validation.js"></script>
<script>
var registerForm = document.getElementById('registerForm');
var validationFunc = function() {
var validation = new Validation();

        validation.add(registerForm.userName, [
        {
            strategy: 'isNonEmpty',
            errMsg:'密碼不能為空'
        },{
            strategy: 'minLength:10',
            errMsg:'密碼長度不能少于10位'
        }]);

        var errMsg = validation.start();
        return errMsg;
    }
    registerForm.onsubmit = function() {
        var errMsg = validationFunc();
        if (errMsg) {
            alert(errMsg);
            return false;
        };

        return false;
    }
</script>

## 代理模式
- *保護代理*:代理可以幫助對象過濾一些請求张抄,用于控制不同權限的對象對目標對象的訪問
- *虛擬代理*:把一些開銷很大的對象延遲到真正需要它的時候才去創(chuàng)建 
- *緩存代理*:為一些開銷大的運算結果提供暫時的存儲,在下次運算時洼怔,如果傳進參數(shù)跟之前的一致署惯,則可以直接返回前面存儲的結果。
- *代理的意義*:
  - 單一原則:指的是就一個類而言镣隶,應該僅有一個引起它變化的原因极谊。如果一個對象承擔了多項職責,就意味著這個對象將變得巨大安岂,引起它變化的原因可能會有多個轻猖。面向對象設計鼓勵將行為分布到細粒度對象之中,如果一個對象承擔的職責過多域那,等于把這些職責耦合到了一起咙边,這種耦合會導致脆弱和低內聚的設計。當變化發(fā)生時面設計可能會遭到意外的破壞

### 1.虛擬代理的案例代碼 -- 實現(xiàn)圖片預加載
- 在圖片加載完畢之前使用loading圖片代替
- ```js
var myImage = (function(){
    var imgNode = document.createElement('img');
    document.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('loading.gif');
            img.src = src;
        }
    }
})();
proxyImage.setSrc('other.png');

2.虛擬代理的案例代碼 -- 合并http請求

  • 用虛擬代理改變點擊checkbox一次發(fā)送一次請求的案例

<body>
<input type="checkbox" value='1'>
<input type="checkbox" value='2'>
<input type="checkbox" value='3'>
<input type="checkbox" value='4'>
<script>
var input = document.getElementsByTagName('input');
for(var i = 0; i < input.length; i++) {
input[i].onclick = function() {
if(this.checked === true) {
proxyFn(this.value)
};
}
}
var proxyFn = (function() {
var cache = [];
var timer = null;
return function(id) {
cache.push(id);
if (timer) {
return;
};
timer = setTimeout(function(){
clearTimeout(timer);
inputEvents(cache);
timer = null;
cache.length = 0;
}, 2000);
}
})();
var inputEvents = function(cache) {
console.log('這是代理后發(fā)送的請求參數(shù)'+ cache);
}
</script>
</body>

### 緩存代理 -- 緩存求乘積的函數(shù)
- ``` js
var mult = function() {
    var a = 1;
    for(var i = 0; i < arguments.length; i++) {
        a = a * arguments[i];
    }
    return a;
}
var proxyMult = (function(){
    var cache = {};
    return function() {
        var args = [].join.call([], arguments);
        if (cache[args]) {
            return cache[args];
        };
        return cache[args] = mult.apply(this, arguments)
    }
})();
//使用次员,第二次直接返回緩存
proxyMult(1,2,3,4)
proxyMult(1,2,3,4)

緩存代理 -- 高階函數(shù)動態(tài)代理

var mult = function() {
var a = 1;
for(var i = 0; i < arguments.length; i++) {
a = a * arguments[i];
}
return a;
}
var plus = function() {
var a = 0;
for(var i = 0; i < arguments.length; i++) {
a = a + arguments[i];
}
return a;
}
var proxyFactory = (function(){
var cache = {};
return function(fn) {
var args = [].join.call([], arguments);
if (cache[args]) {
return cache[args];
};
return cache[args] = fn.apply(this, arguments)
}
})();
//使用
var proxyMult = proxyFactory(mult);
var proxyPlus = proxyFactory(plus);

## 迭代器模式
- 迭代器模式是指提供一種方法順序訪問一個聚合對象中的各種元素败许,而又不需要暴露該對象的內部表示。
- 模式jq實現(xiàn)each
- ```js
function each(ary, fn) {
            for(var i = 0; i < ary.length; i++) {
                fn.call(ary[i], i, ary[i]);
            }
        }
  • 加入中止條件的迭代器

function each(ary, fn) {
for(var i = 0; i < ary.length; i++) {
if( fn.call(ary[i], ary[i], i) === false) {
break;
}
}
}
//使用
each([1,2,3], function(value, i) {
if (value > 2) {
return false;
};
console.log(value)
});

- 迭代器可以分為內部迭代器和外部迭代器
  - 上面的實現(xiàn)是內部迭代器淑蔚,內部迭代器調用非常方便市殷,外界不用關心迭代器內部的實現(xiàn),跟迭代器的交互僅是一次初始調用束倍,這也是內部迭代器的缺點被丧,由于內部迭代器的迭代規(guī)則已經(jīng)被規(guī)定盟戏,上面的函數(shù)無法同時迭代兩個數(shù)組
- 外部迭代器,比較兩個數(shù)組等
- ```js
var Iterator = function(obj) {
            var current = 0;
            var next = function() {
                current += 1;
            }
            var isDone = function() {
                return obj.length <= current;
            }
            var getItem = function() {
                return obj[current];
            }
            return {
                next: next,
                isDone: isDone,
                getItem: getItem
            }
        }
    function compare(iterator1, iterator2s) {
            while(!iterator2.isDone() && !iterator1.isDone()) {
                if( iterator1.getItem() !== iterator2.getItem() ) {
                    console.log('不相等');
                    break;
                }
                console.log(iterator1.getItem())
                iterator1.next();
                iterator2.next();

            }
        }
        var iterator1 = Iterator([2,3,4,5]);
        var iterator2 = Iterator([2,3,3,5]);
        compare(iterator1, iterator2);
  • 迭代器應用舉例
  • 根據(jù)不同瀏覽器獲取相應的上傳組件對象
  • 重構前示例代碼:多個條件分支,擴展性差
  • var getUploadObj = function() {
              try {
                  return new ActiveObject('TXFTNActiveX.FTNUpload');
              } catch (e) {
                  if (supportFlash()) {
                      var str = '<object type="application/x-shockwave-flash"></object>';
                      return $('str').appendTo($('body'));
                  } else {
                      var str = '<input type="file" name="file">';
                      return $('str').appendTo($('body'));
                  }
              }
    

- 用迭代器模式重構后
- ```js
function getActiveUploadObj() {
            try {
                return new ActiveObject('TXFTNActiveX.FTNUpload');
            } catch (e) {
                return false;
            }
        }
        function getFlashUploadObj() {
            if (supportFlash()) {
                var str = '<object type="application/x-shockwave-flash"></object>';
                return $('str').appendTo($('body'));
            }
            return false;
        }
        function getFormUploadObj() {
            var str = '<input type="file" name="file">';
                return $('str').appendTo($('body'));
        }

        var iteratorUpLoadObj = function() {
            var uploadObj = null;
            for(var i = 0; i < arguments.length; i++) {
                uploadObj = arguments[i]();
                if (uploadObj !=== false) {
                    return uploadObj;
                };
            }
        }
        var uploadObj = iteratorUpLoadObj(getActiveUploadObj, getFormUploadObj, getFormUploadObj);

發(fā)布與訂閱模式 -- 觀察者模式

  • 定義對象間一對多的依賴關系甥桂,當一個對象狀態(tài)發(fā)生改變時柿究,所有依賴于它的對象都將得到通知
  • 自定義事件 -- 最簡單的觀察者模式

var Event = {
list: [],
listen: function(fn) {
this.list.push(fn);
},
trigger: function() {
for(var i = 0; i < this.list.length; i++) {
this.list[i].apply(this, arguments);
}
}
}
Event.listen(function() {
console.log('first');
console.log(arguments);
});
Event.listen(function() {
console.log('second');
console.log(arguments);
});
Event.trigger('sss','ddd');
Event.trigger('sss');

- 改進版: 指定key訂閱
- ```js
    var Event = {
        list: {},
        listen: function(key, fn) {
            if (!this.list[key]) {
                this.list[key] = [];
            };
            this.list[key].push(fn);
        },
        trigger: function() {
            var key = [].shift.call(arguments);
            if (!this.list || !this.list[key]) {
                return;
            };
            for(var i = 0; i < this.list[key].length; i++) {
                this.list[key][i].apply(this, arguments);
            }
        }
    }
    Event.listen('first', function() {
        console.log(arguments);
    });
    Event.listen('second', function() {
        console.log(arguments);
    });
    Event.trigger('first', 'sss','ddd');
    Event.trigger('second','sss');
  • 給所有對象都動態(tài)安裝訂閱-發(fā)布模式的函數(shù)

var newObj = Object.create(Event);
newObj.listen('newObj', function() {
console.log(arguments);
});
newObj.trigger('newObj', 'sss','ddd');

- 增加取消訂閱事件
- ```js
Event.remove = function(key, fn) {
            var fns = this.list[key];
            if (!fns) {
                return false;
            };
            if (!fn) {
                fns.length = 0;
            } else {
                for(var i = 0; i < fns.length; i++) {
                    if (fn === fns[i]) {
                        fns.splice(i, 1);
                    };
                }
            }
        }
  • 以上模式如果是先發(fā)布后訂閱則收不到消息
  • 事件命名也有可能重復沖突

// 先發(fā)布后訂閱的使用
Event.trigger('click', 1);
Event.listen('click', function(a) {
console.log(a);
});
//使用命名空間
Event.create('nameSpace1').listen(function(a) {
console.log(a);
});
Event.create('nameSpace1').trigger('click', 1);
Event.create('nameSpace2').listen(function(a) {
console.log(a);
});
Event.create('nameSpace2').trigger('click', 2);
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 = 0; i < cache[key].length; i++) {
                    if( cache[key][i] == 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') {
                        offlineStack.length && offlineStack.pop()();
                    } 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);

                    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.on(key, fn, last);
        },
        remove: function() {
            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);
        }
    }

}

})();

## 命令模式
- 案例背景:有數(shù)十個button按鈕的用戶界面,一個人負責繪制這些按鈕黄选,一個人負責編寫點擊后的具體行為蝇摸,兩人約定點擊按鈕時會執(zhí)行回調函數(shù)
- ```js
  var setCommand = function(button, func) {
      button.onclick = function() {
       func(); 
    }
}
  • 定義菜單和子菜單兩個對象以及它們的功能

var MenuBar = {
refresh: function() {
console.log('refresh')
}
}
var SubMenu = {
add: function() {
console.log('add sub')
},
del: function() {
console.log('del')
}
}

- 把行為封裝在命令類中
- ```js
var RefreshMenuBarCommand = function(receiver) {
    return function(){
        receiver.refresh();
    };
}
var AddSubMenuCommand = function(receiver) {
    return function(){
        receiver.add();
    };
}
var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
setCommand(button1, refreshMenuBarCommand);
var addSubMenuCommand = AddSubMenuCommand(MenuBar);
setCommand(button1, addSubMenuCommand);
  • 更明確的表示當前正在使用命令模式或者除了執(zhí)行命令之外,將來有可能提供撤銷的命令等操作办陷, 改動如下

var RefreshMenuBarCommand = function(receiver) {
return {
excute: function(){
receiver.refresh();
}
}
}
var setCommand = function(button,comand) {
button.onclick = function() {
comand.excute();
}
}

## 組合模式
- *宏命令是一組命令的集合貌夕,通過執(zhí)行宏命令的方式,可以一次執(zhí)行一批命令*
- 案例代碼:智能家居命令民镜,關門啡专,開電腦,登錄qq一系列命令
- ```js
var closeDoorCommand = {
    excute: function() {
        console.log('關門');
    }
}
var openPcCommand = {
    excute: function() {
        console.log('開電腦');
    }
}
var loginQQCommand = {
    excute: function() {
        console.log('登錄qq');
    }
}
var MacroCommand = function() {
    return {
        commandList: [],
        add: function(command) {
            this.commandList.push(command);
        },
        excute: function() {
            for(var i = 0, command; command = this.commandList[i++];) {
                command.excute();
            }
        }
    }
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCommand);
macroCommand.add(loginQQCommand);
macroCommand.add(openPcCommand);
macroCommand.excute();
  • marciCommand被稱為組合對象制圈,closeDoorCommand们童、loginQQCommand、openPcCommand都是葉對象
  • 優(yōu)點:
    1鲸鹦、提供一種遍歷樹形結構的方案慧库,通過調用組合對象的excute方法,程序會遞歸調用組合對象下的excute方法馋嗜,組合模式可以非常方便地描述對象部分-整體層次結構齐板。
    2、利用對象多態(tài)性和統(tǒng)一對待組合對象和單個對象
  • 組合模式的樹傳遞:請求從樹最頂端的對象往下傳遞葛菇,如果當前處理請求的對象是葉對象(普通子命令)甘磨,葉對象自身會對請求作出相應的處理。如果當前處理請求的對象是組合對象(宏命令)熟呛,組合對象則會遍歷它屬下的子節(jié)點宽档,將請求繼續(xù)傳遞給子節(jié)點。

更復雜的宏命令

  • 關門庵朝,開電腦,登錄qq為一個子命令又厉,打開電視和音響為一個子命令九府,打開電腦為一個子節(jié)點

var MacroCommand = function() {
return {
commandList: [],
add: function(command) {
this.commandList.push(command);
},
excute: function() {
for(var i = 0, command; command = this.commandList[i++];) {
command.excute();
}
}
}
}
//開空調為子對象
var openAcCommand = {
excute: function() {
console.log('開空調');
}
}
//開電腦、登錄qq覆致、關門組合命令
var closeDoorCommand = {
excute: function() {
console.log('關門');
}
}
var openPcCommand = {
excute: function() {
console.log('開電腦');
}
}
var loginQQCommand = {
excute: function() {
console.log('登錄qq');
}
}
var macroCommand1 = MacroCommand()
macroCommand1.add(closeDoorCommand);
macroCommand1.add(loginQQCommand);
macroCommand1.add(openPcCommand);
//打開電視侄旬、打開音響組合命令
var openTvCommand = {
excute: function() {
console.log('打開電視');
}
}
var openSoudCommand = {
excute: function() {
console.log('打開音響');
}
}
var macroCommand2 = MacroCommand()
macroCommand2.add(openTvCommand);
macroCommand2.add(openSoudCommand);
//總組合命令
var macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
macroCommand.excute();

- 組合模式實例 -- 掃描文件夾
- ```js
var Folder = function(name) {
        this.name = name;
        this.files = []
    }
    Folder.prototype.add = function(file) {
        this.files.push(file);
    }
    Folder.prototype.scan = function() {
        console.log('開始掃描文件夾' + this.name);
        for(var i = 0, file; file = this.files[i++];) {
            file.scan();
        }
    }
    var File = function(name) {
        this.name = name;
    }
    File.prototype.add = function(file) {
        throw new Error('文件下面不能再加文件');
    }
    File.prototype.scan = function() {
        console.log('開始掃描文件' + this.name);
    }
    var folder = new Folder('學習資料');
    var folder1 = new Folder('javascript');
    var folder2 = new Folder('nodejs');
    var file1 = new File('js設計模式');
    var file2 = new File('js高級設計程序');
    var file3 = new File('nodejs深入淺出');
    folder1.add(file1)
    folder1.add(file2)
    folder2.add(file3)
    folder.add(folder1)
    folder.add(folder2)
    folder.scan();
  • 掃描文件增強版 -- 引用父對象,加刪除功能
  • 當this.parent不為null時不做任何操作

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
var Folder = function(name) {
this.name = name;
this.files = [];
this.parent = null;
}
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; file = this.files[i++];) {
file.scan();
}
}
Folder.prototype.remove = function() {
console.log('開始刪除文件夾' + this.name);
if (!this.parent) {
return;
};
var files = this.parent.files;
for(var i = 0, file; i < files.length; i++) {
file = files[i]
if (this === file) {
files.splice(i, 1);
};
}
}
var File = function(name) {
this.name = name;
this.parent = null;
}
File.prototype.add = function(file) {
throw new Error('文件下面不能再加文件');
}
File.prototype.scan = function() {
console.log('開始掃描文件' + this.name);
}
File.prototype.remove = function() {
console.log('開始刪除文件' + this.name);
if (!this.parent) {
return;
};
var files = this.parent.files;
for(var i = 0, file; file = files[i++];) {
if (this === file) {
files.splice(i, 1);
};
}
}
var folder = new Folder('學習資料');
var folder1 = new Folder('javascript');
var folder2 = new Folder('nodejs');
var file1 = new File('js設計模式');
var file2 = new File('js高級設計程序');
var file3 = new File('nodejs深入淺出');
folder1.add(file1)
folder1.add(file2)
folder2.add(file3)
folder.add(folder1)
folder.add(folder2);
folder1.remove();
folder.scan();
</script>
</body>
</html>

## 模板方法模式 -- 繼承
- 模板方法方式是一種只需用繼承就可以實現(xiàn)的簡單模式
- 由兩部分結構:第一部分是抽象父類煌妈,第二部分是具體實現(xiàn)的實現(xiàn)子類
- 案例代碼:咖啡和茶
  - 咖啡需求:
    1儡羔、把水煮沸
    2宣羊、用沸水沖泡咖啡
    3、把咖啡倒進杯子
    4汰蜘、加糖和牛奶
- ```js
var Coffee = function() {}
    Coffee.prototype.boilWater = function() {
        console.log('把水煮沸');
    }
    Coffee.prototype.brewCoffee = function() {
        console.log('沸水沖泡咖啡');
    }
    Coffee.prototype.pourInCup = function() {
        console.log('把咖啡倒進杯子');
    }
    Coffee.prototype.addSugarAndMilk = function() {
        console.log('加糖和牛奶');
    }
    Coffee.prototype.init = function() {
        this.boilWater();
        this.brewCoffee();
        this.pourInCup();
        this.addSugarAndMilk();
    }
  • 茶葉需求:
    1仇冯、把水煮沸
    2、用沸水浸泡咖啡
    3族操、把茶水倒進杯子
    4苛坚、加lemon

var Tea = function() {}
Tea.prototype.boilWater = function() {
console.log('把水煮沸');
}
Tea.prototype.steepTea = function() {
console.log('用沸水浸泡茶葉');
}
Tea.prototype.pourInCup = function() {
console.log('把茶水倒進杯子');
}
Tea.prototype.addLemon = function() {
console.log('加檸檬');
}
Tea.prototype.init = function() {
this.boilWater();
this.steepTea();
this.pourInCup();
this.addLemon();
}

- 抽離需求共同點
  1、把水煮沸
  2色难、用沸水沖泡飲料
  3泼舱、把飲料倒進杯子
  4、加調料
- ```js
var Common = function() {}
    Common.prototype.boilWater = function() {
        console.log('把水煮沸');
    }
    Common.prototype.brew = function() {
        throw new Error('子類必須重寫brew方法');
    }
    Common.prototype.pourInCup = function() {
        throw new Error('子類必須重寫pourInCup方法');
    }
    Common.prototype.addOthers = function() {
        throw new Error('子類必須重寫addOthers方法');
    }
    Common.prototype.init = function() {
        this.boilWater();
        this.brew();
        this.pourInCup();
        this.addOthers();
    }
    //創(chuàng)建Coffee子類和Tea子類
    var Coffee = function() {}
    Coffee.prototype = new Common();
    Coffee.prototype.brew = function() {
        console.log('沸水沖泡咖啡');
    }
    Coffee.prototype.pourInCup = function() {
        console.log('把咖啡倒進杯子');
    }
    Coffee.prototype.addOthers = function() {
        console.log('加糖和牛奶');
    }

    var coffee = new Coffee();
    coffee.init();

    var Tea = function() {}
    Tea.prototype = new Common();
    Tea.prototype.brew = function() {
        console.log('沸水沖泡茶葉');
    }
    Tea.prototype.pourInCup = function() {
        console.log('把茶水倒進杯子');
    }
    Tea.prototype.addOthers = function() {
        console.log('加檸檬');
    }

    var tea = new Tea();
    tea.init();
  • 模板方法模式常被架構師用于搭建項目的框架枷莉,架構師定好了框架的骨架娇昙,程序員繼承框架的結構之后負責往里面填空。

鉤子方法

  • 假如需求中某些需求是不需要加其他東西的笤妙,則鉤子方法可以用來解決這個問題

Common.prototype.init = function() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.isNeedOthers()) {
this.addOthers();
};

}
- 在javascript中的繼承
- ```js
  var Common = function(params) {
            var boilWater = function() {
                console.log('把水煮沸');
            }
            var brew = params.brew || function() {
                throw new Error('子類必須重寫brew方法');
            }
            var pourInCup = params.pourInCup || function() {
                throw new Error('子類必須重寫pourInCup方法');
            }
            var addOthers = params.addOthers || pourInCup function() {
                throw new Error('子類必須重寫addOthers方法');
            }
            var isNeedOthers =  params.isNeedOthers || function() {
                return true;
            }
            var F = function() {}
            F.prototype.init = function() {
                this.boilWater();
                this.brew();
                this.pourInCup();
                if (this.isNeedOthers()) {
                    this.addOthers();
                };
            }

            return F;
        }
        var Coffee = Common({
            brew: function() {
                console.log('把咖啡倒進杯子');
            },
            pourInCup: function() {
                console.log('把咖啡倒進杯子');
            },
            addOthers: function() {
                console.log('加糖和牛奶');
            },
            isNeedOthers: function() {
                return false;
            }

        })
        var coffee = new Coffee();
        coffee.init();

享元模式

  • 運用共享技術來支持大量細粒度的對象
  • 適用場景:系統(tǒng)中因為大量類似的對象而導致內存占用過高冒掌,瀏覽器特別是移動端的瀏覽器分配的內存并不算多的時候
  • 案例:服裝廠有50中男士衣服和50種女士衣服,需要50個男模特和50個女模特分別穿上一件衣服拍照危喉,不使用享元模式的情況下:

var Model = function(sex, clothes) {
this.sex = sex;
this.clothes = clothes;
}
Model.prototype.takePhoto = function() {
console.log('sex=' + this.sex + 'clothes=' + clothes)
}

    for(var i = 0; i < 50; i++) {
        var maleModel = new Model('male', 'clothes' + i);
        maleModel.takePhoto();
    }

    for(var i = 0; i < 50; i++) {
        var femaleModel = new Model('female', 'clothes' + i);
        femaleModel.takePhoto();
    }
- 問題:存在太多的對象占用內存
- 解決思路:男女模特只需一個來試穿男士衣服和女士衣服
- ```js
var Model = function(sex) {
            this.sex = sex;
        }
        Model.prototype.takePhoto = function() {
            console.log('sex=' + this.sex + 'clothes=' + clothes)
        }
        var maleModel = new Model('male');
        var femaleModel = new Model('female');
        for(var i = 0; i < 50; i++) {
            maleModel.clothes = 'clothes' + i
            maleModel.takePhoto();
        }

        for(var i = 0; i < 50; i++) {
            femaleModel.clothes = 'clothes' + i
            femaleModel.takePhoto();
        }

享元模式的內部狀態(tài)和外部狀態(tài)

  • 享元模式要求將對象的屬性劃分為內部狀態(tài)和外部狀態(tài)(狀態(tài)在這里通常指屬性)宋渔。享元模式的目標是盡量減少共享對象的數(shù)量。
    • 內部狀態(tài)存儲于對象內部
    • 內部狀態(tài)可以被一些對象共享
    • 內部狀態(tài)獨立于具體的場景辜限,通常不會改變(這里sex是內部屬性即內部狀態(tài))
    • 外部狀態(tài)取決于具體的場景皇拣,并根據(jù)場景而變化,外部狀態(tài)不能共享(這里的clothes是外部屬性即外部狀態(tài))

享元模式的問題

1薄嫡、代碼中顯式創(chuàng)建了兩個共享對象氧急,但是實際中并不一定用到兩個共享對象;用對象工廠來解決這個問題毫深,當共享對象真正被需要的時候才去創(chuàng)建
2吩坝、手動設置clothes屬性在更復雜的系統(tǒng)中并不是一個好的方式,由于的系統(tǒng)的復雜導致與共享對象的聯(lián)系變得困難哑蔫;用一個管理器來記錄對象相關的外部狀態(tài)钉寝,使這些外部狀態(tài)通過某個鉤子和共享對象聯(lián)系起來。

  • 實際案例 - 文件上傳
    • 上傳文件數(shù)量過多時對象爆炸, 假設有flash上傳和插件上傳兩種

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.create('div');
this.dom.innerHTML = '<span>文件名稱:'+this.fileName+'文件大小'+ this.fileSize +'</span><button class="delFile"></button>';
this.dom.querySelector('.delFile').onclick = function() {
that.delFile();
}

        document.appendChild(this.dom);
    }
    Upload.prototype.delFile = function() {
        if (this.fileSize < 3000) {
            return this.dom.parentNode.removeChild(this.dom);
        };
        if (window.confirm('確定要刪除該文件嗎闸迷?'+ fileName)) {
            return this.dom.parentNode.removeChild(this.dom);
        };
        
    }
    var id = 0;
    var startUpload = function(uploadType, files) {
        for(var i = 0, file; file = files[i++];) {
            var uploadObj = new upload(uploadType, file.fileName, file.fileSize);
            uploadObj.init(id++);
        }
    }
    //創(chuàng)建3個插件上傳對象和3個flash上傳對象
    startUpload('plugin', [{
        fileName: '1.txt',
        fileSize: 1000
    },{
        fileName: '2.txt',
        fileSize: 3000
    },
    {
        fileName: '3.txt',
        fileSize: 5000
    }]);

    startUpload('flash', [{
        fileName: '4.txt',
        fileSize: 1000
    },{
        fileName: '5.txt',
        fileSize: 3000
    },
    {
        fileName: '6.txt',
        fileSize: 5000
    }]);
- 文件上傳享元模式重構
- 劃分內部狀態(tài)是uploadType嵌纲,因為一旦明確了uploadType,無論我們使用什么方式上傳腥沽,這個上傳對象都是可以被任何文件共用的逮走,而filename和filesize是根據(jù)場景而變化的,每個文件的filename和filesize都不一樣今阳,沒有辦法被共享师溅,只能被劃分到外部狀態(tài)茅信。
- 剝離外部狀態(tài)
- ```js
  var Upload = function(uploadType) {
            this.uploadType = uploadType;
        }

        //不需要init方法,初始化的upload對象由管理器管理
        //開始刪除之前需要讀取文件的實際大小墓臭,而文件的實際大小被儲存在外部管理器uploadManager
        Upload.prototype.delFile = function() {
            uploadManager.setExtetnalState(id, this);
            if (this.fileSize < 3000) {
                return this.dom.parentNode.removeChild(this.dom);
            };
            if (window.confirm('確定要刪除該文件嗎蘸鲸?'+ fileName)) {
                return this.dom.parentNode.removeChild(this.dom);
            };
            
        }
  • 工廠進行對象實例化,如果某種內部狀態(tài)對應的共享對象已經(jīng)被創(chuàng)建過起便,那么直接返回這個對象

var UploadFactory = (function() {
var createFlyWeightObjs = {};
return {
create: function(uploadType) {
if (createFlyWeightObjs[uploadType]) {
return createFlyWeightObjs[uploadType]
};
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})()

- 管理器封裝外部狀態(tài)棚贾,它負責向UploadFactory提交創(chuàng)建對象的請求,并用一個uploadDatabase對象保存所有的upload對象的外部狀態(tài)
- ```js
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>文件名稱:'+this.fileName+'文件大小'+ this.fileSize +'</span><button class="delFile"></button>';
                    dom.querySelector('.delFile').onclick = function() {
                        flyWeightObj.delFile();
                    }
                    document.appendChild(dom);
                    uploadDatabase[id] = {
                        fileName: fileName,
                        fileSize: fileSize,
                        dom: dom
                    }
                    return flyWeightObj;
                },
                setExtetnalState: function(id, flyWeightObj) {
                    var uploadData = uploadDatabase[id];
                    for(var i in uploadData) {
                        flyWeightObj[i] = uploadData[i];
                    }
                }
            }
        })();
        var startUpload = function(uploadType, files) {
            for(var i = 0, file; file = files[i++];) {
                var uploadObj = uploadManager.add(id++, uploadType, file.fileName, file.fileSize);
            }
        }
        //創(chuàng)建3個插件上傳對象和3個flash上傳對象
        startUpload('plugin', [{
            fileName: '1.txt',
            fileSize: 1000
        },{
            fileName: '2.txt',
            fileSize: 3000
        },
        {
            fileName: '3.txt',
            fileSize: 5000
        }]);

        startUpload('flash', [{
            fileName: '4.txt',
            fileSize: 1000
        },{
            fileName: '5.txt',
            fileSize: 3000
        },
        {
            fileName: '6.txt',
            fileSize: 5000
        }]);
  • 享元模式的適用性
    1榆综、一個程序中使用了大量的相似對象
    2妙痹、由于使用了大量的對象,造成很大的內存開銷
    3鼻疮、對象的大多數(shù)狀態(tài)都可以變?yōu)橥獠繝顟B(tài)
    4怯伊、剝離出對象的外部狀態(tài)之后,可以用相對較少的共享對象取代大量對象
  • 當對象沒有內部狀態(tài)的時候判沟,生產(chǎn)共享對象的工廠實際上變成了一個單例工廠

對象池

  • 對象池維持一個裝載空閑對象的池子耿芹,如果需要對象的時候,不是直接new挪哄,而是轉從對象池里獲取吧秕。如果對象池里沒有空閑對象則創(chuàng)建一個新對象,當獲取出的對象完成它的職責之后迹炼,再進入池子等待被下次獲取
  • 對象池使用最多的場景是跟dom操作有關的砸彬。很多空間和時間都消耗在了DOM節(jié)點上
  • 實際案例:mobike單車的定位,某一次的定位單車小圖標可以與第二次的單車有重復的節(jié)點

var toolTipFactory = (function(){
var toolTipPool = [];
return {
create: function() {
if (toolTipPool.length === 0) {
var div = document.createElement('div');
document.body.appendChild(div);
return div;
} else {
return toolTipPool.shift();
}
},
recover: function(toolTipDom) {
return toolTipPool.push(toolTipDom);
}
}
})();
//創(chuàng)建2個圖標節(jié)點斯入,用一個數(shù)組記錄方便回收
var ary = [];
for(var i = 0, str; str = ['A', 'B'][i++];) {
var toolTip = toolTipFactory.create();
toolTipPool.innerHTML = str;
ary.push(toolTip);
}
//第二次搜索重新繪制之前將兩個節(jié)點回收進對象池
for(var i = 0, toolTip; toolTip = ary[i++];) {
toolTipFactory.recover(toolTip);
}
//再創(chuàng)建6個小氣泡
var ary = [];
for(var i = 0, str; str = ['A', 'B','C', 'D','E','F'][i++];) {
var toolTip = toolTipFactory.create();
toolTipPool.innerHTML = str;
ary.push(toolTip);
}

- 通用對象池實現(xiàn)
-  ```js
var toolTipFactory = function(fn){
            var toolTipPool = [];
            return {
                create: function() {
                    var obj = toolTipPool.length === 0 ? fn.apply(this, arguments) : toolTipPool.shift();
                    return obj;
                },
                recover: function(toolTipDom) {
                    return toolTipPool.push(toolTipDom);
                }
            }
        };
        var createDivFactory = toolTipFactory(function(str){
            var div = document.createElement('div');
                div.innerHTML = str;
                document.body.appendChild(div);
                return div;
        });
        var ary = [];
        for(var i = 0, str; str = ['A', 'B'][i++];) {
            var toolTip = createDivFactory.create(str);
            ary.push(toolTip);
        }

職責鏈模式

  • 使多個對象都有機會處理請求砂碉,從而避免請求的發(fā)送者和接受者之間的耦合關系,將這些對象連成一條鏈刻两,并沿著這條鏈傳遞該請求增蹭,直到有一個對象處理它為止。
  • 情景案例:公司對支付過定金的用戶有一定的優(yōu)惠政策磅摹。在正式購買之后滋迈,已經(jīng)支付500元定金的用戶會收到100元商城優(yōu)惠券,200元定金的用戶可以收到50元優(yōu)惠券户誓,而沒有支付定金的普通用戶沒有優(yōu)惠券休蟹。
    • 后臺的字段:
      • orderType: 訂單類型砂代,1,500元的定金用戶;2,200元的定金用戶瘾英;3:普通用戶
      • pay:是否已經(jīng)支付定金
      • stock 表示當前用哪個與普通購買的手機庫存量橙依,已經(jīng)支付過500或者200定金的用戶不受此限制
  • 代碼示例

var order = function(orderType, pay, stock) {
if (orderType === 1) {
if (pay === true) {
console.log('500元定金已付证舟,得到100優(yōu)惠券');
} else {
if (stock > 0) {
console.log('普通購買模式硕旗,無優(yōu)惠券');
} else {
console.log('手機庫存不足');
}
}
} else if( orderType === 2) {
if (pay === true) {
console.log('200元定金已付,得到50優(yōu)惠券');
} else {
if (stock > 0) {
console.log('普通購買模式女责,無優(yōu)惠券');
} else {
console.log('手機庫存不足');
}
}
} else if( orderType === 3) {
if (stock > 0) {
console.log('普通購買模式漆枚,無優(yōu)惠券');
} else {
console.log('手機庫存不足');
}
}
}
orderType(1, true, 500)

- 重構代碼:將500元訂單、200元訂單以及普通購買分成3個函數(shù)抵知,先將3個字段傳給500元訂單函數(shù)墙基,如果不符合處理條件,則將請求傳遞給后面的200元訂單函數(shù)刷喜,如果200元訂單函數(shù)依然不能處理該請求残制,則繼續(xù)傳遞請求給普通函數(shù)

-   ```js
var order500 = function(orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log('500元定金已付,得到100優(yōu)惠券');
        } else {
            order200(orderType, pay, stock);
        }
    }
    var order200 = function(orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log('200元定金已付掖疮,得到50優(yōu)惠券');
        } else {
            orderNormal(orderType, pay, stock);
        }
    }
    var orderNormal = function(orderType, pay, stock) {
        if (stock > 0) {
            console.log('普通購買模式初茶,無優(yōu)惠券');
        } else {
            console.log('手機庫存不足');
        }
    }
    order500(1, true, 500); //500元定金已付,得到100優(yōu)惠券
    order500(1, false, 500);//普通購買模式浊闪,無優(yōu)惠券
    order500(2, true, 500); //200元定金已付恼布,得到50優(yōu)惠券
    order500(2, false, 500); //普通購買模式,無優(yōu)惠券
    order500(3, false, 0); //手機庫存不足
  • 重構后的代碼仍然存在傳遞代碼被耦合在業(yè)務函數(shù)中
  • 改寫三個函數(shù)搁宾,約定如果不能處理請求則返回一個特定的字符串表示該請求需要往后面?zhèn)鬟f折汞,這里約定為‘next’

var order500 = function(orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500元定金已付,得到100優(yōu)惠券');
} else {
return 'next';
}
}
var order200 = function(orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200元定金已付盖腿,得到50優(yōu)惠券');
} else {
return 'next';
}
}
var orderNormal = function(orderType, pay, stock) {
if (stock > 0) {
console.log('普通購買模式爽待,無優(yōu)惠券');
} else {
console.log('手機庫存不足');
}
}

- 定義一個構造函數(shù)Chain傳入需要被包裝的函數(shù),擁有一個實例屬性this.successor奸忽,表示在鏈中的下一個節(jié)點
- ```js
var Chain = function(fn) {
        this.fn = fn;
        this.successor = null;
    }

    Chain.prototype.setNext = function(successor) {
        return this.successor = successor;
    }

    Chain.prototype.passRequset = function() {
        var result = this.fn.apply(this, arguments);
        if (result === 'next') {
            return this.successor && this.successor.passRequset.apply(this.successor, arguments);
        };
        return result;
    }
    var chainOrder500 = new Chain(order500);
    var chainOrder200 = new Chain(order200);
    var chainOrderNormal = new Chain(orderNormal);
    chainOrder500.setNext(chainOrder200);
    chainOrder200.setNext(chainOrderNormal);
    chainOrder500.passRequset(1, true, 500); //500元定金已付堕伪,得到100優(yōu)惠券
    chainOrder500.passRequset(1, false, 500);//普通購買模式,無優(yōu)惠券
    chainOrder500.passRequset(2, true, 500); //200元定金已付栗菜,得到50優(yōu)惠券
    chainOrder500.passRequset(2, false, 500); //普通購買模式欠雌,無優(yōu)惠券
    chainOrder500.passRequset(3, false, 0); //手機庫存不足
  • 職責鏈的異步形式
  • 增加一個主動觸發(fā)的函數(shù)在異步調用時使用
  • var fn1 = new Chain(function() {
          console.log(1);
          return 'next';
      });
      var fn2 = new Chain(function() {
          console.log(2);
          var self = this;
          setTimeout(function() {
              self.next();
          }, 1000);
      });
      var fn3 = new Chain(function() {
          console.log(3);
      });
      fn1.setNext( fn2).setNext(fn3);
      fn1.passRequset();
    

## 中介者模式
- 作用是解除對象與對象之間的緊耦合關系。增加一個中介者對象之后疙筹,所有的相關對象都通過中介者對象來通信富俄,而不是互相引用,所以當一個對象發(fā)生改變時而咆,只需要通知中介者對象即可霍比。
- 案例代碼:泡泡堂游戲
- ```js
// version: 兩個玩家版本
    function Player(name) {
        this.name = name;
        this.enemy = null;
    }
    Player.prototype.win = function() {
        console.log(this.name + 'win');
    }
    Player.prototype.lose = function() {
        console.log(this.name + 'lose');
    }
    Player.prototype.die = function() {
        this.lose();
        this.enemy.win();
    }
    //創(chuàng)建兩個玩家
    var player1 = new Player('one');
    var player2 = new Player('two');

    //互相設置敵人
    player1.enemy = player2;
    player2.enemy = player1;

    player1.die(); //輸出:one lose, two win
  • 為游戲增加隊伍

var players = [];
function Player(name, teamColor) {
this.partners = [];
this.enemies = [];
this.state = 'live';
this.name = name;
this.teamColor = teamColor;
}
Player.prototype.win = function() {
console.log(this.name + 'win');
}
Player.prototype.lose = function() {
console.log(this.name + 'lose');
}
// 每個玩家死亡的時候都遍歷其他隊友的生存狀況
Player.prototype.die = function() {
var all_dead = true;

    this.state = 'dead';
    for (var i = 0, partner; partner = this.partners[i++];) {
        if (partner.state !== 'dead') {
            all_dead = false;
            break;
        };
    }
    if (all_dead === true) {
        this.lose();
        for (var i = 0, partner; partner = this.partners[i++];) {
            partner.lose();
        }
        for (var i = 0, enemy; enemy = this.enemies[i++];) {
            enemy.win();
        }
    };
}
var playerFactory = function(name, teamColor) {
    var newPlayer = new Player(name, teamColor);
    for(var i = 0, player; player = players[i++];) {
        if (player.teamColor === newPlayer.teamColor) {
            player.partners.push(newPlayer);
            newPlayer.partners.push(player);
        } else {
            player.enemies.push(newPlayer);
            newPlayer.enemies.push(player);
        }
    }

    players.push(newPlayer);
    return newPlayer;
}
var player1 = playerFactory(1, 'red');
var player2 = playerFactory(2, 'red');
var player3 = playerFactory(3, 'red');
var player4 = playerFactory(4, 'red');

var player5 = playerFactory(5, 'blue');
var player6 = playerFactory(6, 'blue');
var player7 = playerFactory(7, 'blue');
var player8 = playerFactory(8, 'blue');

player1.die()
player2.die()
player3.die()
player4.die()
- 玩家增多帶來的麻煩
 - 當每個對象狀態(tài)發(fā)生改變都必須要顯式地遍歷通知其他對象
- 用中介者模式改造游戲
- ```js
function Player(name, teamColor) {
        this.name = name;
        this.teamColor = teamColor;
        this.state = 'alive';
    }
    Player.prototype.win = function() {
        console.log(this.name + 'win');
    }
    Player.prototype.lose = function() {
        console.log(this.name + 'lose');
    }
    //改為向中介者發(fā)送死亡消息
    Player.prototype.die = function() {
        this.state = 'dead';
        playerDirector.ReceiveMessage('playerDead', this);
    }

    Player.prototype.remove = function() {
        playerDirector.ReceiveMessage('removePlayer', this);
    }
    Player.prototype.changeTeam = function(color) {
        playerDirector.ReceiveMessage('changeTeam', this, color);
    }
    //重寫工廠函數(shù)
    var playerFactory = function(name, teamColor) {
        var newPlayer = new Player(name, teamColor);
        playerDirector.ReceiveMessage('addPlayer', newPlayer);
        return newPlayer;
    }
    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();
                }
                for(var color in players) {
                    if (color !== teamColor) {
                        var otherTeam = players[color];
                        for(var i =0, player; player = otherTeam[i++];) {
                            player.win();
                        }
                    };
                }
            };
        }
        var ReceiveMessage = function() {
            var message = Array.prototype.shift.call(arguments);
            operations[message].apply(this, arguments);
        }

        return {
            ReceiveMessage: ReceiveMessage
        }
    })()

    var player1 = playerFactory(1, 'red');
    var player2 = playerFactory(2, 'red');
    var player3 = playerFactory(3, 'red');
    var player4 = playerFactory(4, 'red');

    var player5 = playerFactory(5, 'blue');
    var player6 = playerFactory(6, 'blue');
    var player7 = playerFactory(7, 'blue');
    var player8 = playerFactory(8, '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()
  • 實例:購買商品
    • 在購買手機的流程中可以選擇手機的顏色以及輸入購買數(shù)量暴备,同時頁面中有兩個展示區(qū)域悠瞬,分別向用戶展示剛剛選擇好的數(shù)量和顏色

<body>
<select id="colorSelect">
<option value="">請選擇</option>
<option value="red">紅色</option>
<option value="blue">藍色</option>
</select>
輸入購買數(shù)量:<input type="text" id="numberInput">
你選了顏色是:<div id="colorInfo"></div>
你選了數(shù)量是:<div id="numberInfo"></div>

<button id="nextBtn" disabled="true">請選擇手機顏色和購買數(shù)量</button>
<script>

var colorSelect = document.getElementById('colorSelect');
var numberInput = document.getElementById('numberInput');
var colorInfo = document.getElementById('colorInfo');
var numberInfo = document.getElementById('numberInfo');
//假設我們提前從后臺獲取到了所有顏色手機的庫存量
var goods = {
    'red': 3,
    'blue': 6
}
/*
 *可能會出現(xiàn)以下情況
 *選擇紅色手機,購買4個庫存不足
 *選擇藍色手機,購買5個庫存不足浅妆,可以加入購物車
 *沒有輸入購買數(shù)量的時候望迎,按鈕將被禁用并顯示相應提示
*/
    // 顏色選擇框事件變化
colorSelect.onchange = function() {
    var color = this.value,
        number = numberInput.value,
        stock = goods[color];

    colorInfo.innerHTML = color;
    numberInfo.innerHTML = number;
    if (!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請選擇手機顏色';
    };

    if (( (number - 0 ) | 0) !== number - 0) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
        return;
    };
    if (number > stock) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '庫存不足';
        return;
    };
    nextBtn.disabled = false;
    nextBtn.innerHTML = '放入購物車';
}

//數(shù)量輸入框的改變
numberInput.oninput = function() {
var color = this.value,
number = numberInput.value,
stock = goods[color];

    numberInfo.innerHTML = number;

    if (!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請選擇手機顏色';
    };

    if (( (number - 0 ) | 0) !== number - 0) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
        return;
    };
    if (number > stock) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = '庫存不足';
        return;
    };
    nextBtn.disabled = false;
    nextBtn.innerHTML = '放入購物車';
}
</script>

</body>


![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2986255-2703eb84de135da2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 可能面臨的問題
  - 如果要改變展示區(qū)域就要深入到事件內部代碼修改
  - 當增加判斷條件是要每個事件都去判斷,如:增加手機內存的選擇凌外,每個事件都要增加判斷條件辩尊,同時要新增手機內存的選擇事件
- 增加內存判斷后的代碼:
- ```html
<body>
    <select id="colorSelect">
        <option value="">請選擇</option>
        <option value="red">紅色</option>
        <option value="blue">藍色</option>
    </select>
    <select id="memorySelect">
        <option value="">請選擇</option>
        <option value="32G">32G</option>
        <option value="64G">64G</option>
    </select>
    輸入購買數(shù)量:<input type="text" id="numberInput">
    你選了顏色是:<div id="colorInfo"></div>
    你選了內存是:<div id="memoryInfo"></div>
    你選了數(shù)量是:<div id="numberInfo"></div>

    <button id="nextBtn" disabled="true">請選擇手機顏色和購買數(shù)量</button>
    <script>

    var colorSelect = document.getElementById('colorSelect');
    var memorySelect = document.getElementById('memorySelect');
    var numberInput = document.getElementById('numberInput');
    var colorInfo = document.getElementById('colorInfo');
    var numberInfo = document.getElementById('numberInfo');
    var memoryInfo = document.getElementById('memoryInfo');
    //假設我們提前從后臺獲取到了所有顏色手機的庫存量
    var goods = {
        'red|32G': 3,
        'red|64G': 0,
        'blue|32G': 1,
        'blue|64G': 6
    }
    colorSelect.onchange = function() {
        var color = this.value,
            memory = memorySelect.value,
            number = numberInput.value,
            stock = goods[color + '|' + memory];

        colorInfo.innerHTML = color;
        
        if (!color) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇手機顏色';
        };

        if (!memory) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇內存大小';
        };

        if (( (number - 0 ) | 0) !== number - 0) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
            return;
        };
        if (number > stock) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '庫存不足';
            return;
        };
        nextBtn.disabled = false;
        nextBtn.innerHTML = '放入購物車';
    }
    numberInput.oninput = function() {
        var color = this.value,
            memory = memorySelect.value,
            number = numberInput.value,
            stock = goods[color + '|' + memory];

        numberInfo.innerHTML = number;

        if (!color) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇手機顏色';
        };

        if (!memory) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇內存大小';
        };

        if (( (number - 0 ) | 0) !== number - 0) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
            return;
        };
        if (number > stock) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '庫存不足';
            return;
        };
        nextBtn.disabled = false;
        nextBtn.innerHTML = '放入購物車';
    }
    //新增手機內存下拉框選擇事件
    memorySelect.onchange = function() {
        var color = this.value,
            memory = memorySelect.value,
            number = numberInput.value,
            stock = goods[color + '|' + memory];

        memoryInfo.innerHTML = memory;
        if (!color) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇手機顏色';
        };

        if (!memory) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請選擇內存大小';
        };

        if (( (number - 0 ) | 0) !== number - 0) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
            return;
        };
        if (number > stock) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = '庫存不足';
            return;
        };
        nextBtn.disabled = false;
        nextBtn.innerHTML = '放入購物車';
    }
    </script>
</body>
  • 每個對象都耦合在一起,改變或者增加任何一個節(jié)點對象都要通知到與其相關的對象

var goods = {
'red|32G': 3,
'red|64G': 0,
'blue|32G': 1,
'blue|64G': 6
}
var mediator = (function(){
var colorSelect = document.getElementById('colorSelect');
var memorySelect = document.getElementById('memorySelect');
var numberInput = document.getElementById('numberInput');
var colorInfo = document.getElementById('colorInfo');
var numberInfo = document.getElementById('numberInfo');
var memoryInfo = document.getElementById('memoryInfo');

    return {
        changed: function(obj) {
            var color = this.value,
            memory = memorySelect.value,
            number = numberInput.value,
            stock = goods[color + '|' + memory];

            if (obj === colorSelect) {
                colorInfo.innerHTML = color;
            } else if(obj === memorySelect) {
                memoryInfo.innerHTML = memory;
            } else if(obj === numberInput) {
                numberInput.innerHTML = number;
            }
            if (!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請選擇手機顏色';
            };

            if (!memory) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請選擇內存大小';
            };

            if (( (number - 0 ) | 0) !== number - 0) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
                return;
            };
            if (number > stock) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '庫存不足';
                return;
            };
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入購物車';
        }
    }
})()

colorSelect.onchange = function() {
    mediator.changed(this);
}

memorySelect.onchange = function() {
    mediator.changed(this);
}
numberInput.oninput = function() {
    mediator.changed(this);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末康辑,一起剝皮案震驚了整個濱河市摄欲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌疮薇,老刑警劉巖胸墙,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惦辛,居然都是意外死亡劳秋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門胖齐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玻淑,“玉大人,你說我怎么就攤上這事呀伙〔孤模” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵剿另,是天一觀的道長箫锤。 經(jīng)常有香客問我,道長雨女,這世上最難降的妖魔是什么谚攒? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮氛堕,結果婚禮上馏臭,老公的妹妹穿的比我還像新娘。我一直安慰自己讼稚,他們只是感情好括儒,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锐想,像睡著了一般帮寻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赠摇,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天固逗,我揣著相機與錄音浅蚪,去河邊找鬼。 笑死抒蚜,一個胖子當著我的面吹牛掘鄙,可吹牛的內容都是我干的。 我是一名探鬼主播嗡髓,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼收津!你這毒婦竟也來了饿这?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撞秋,失蹤者是張志新(化名)和其女友劉穎长捧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吻贿,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡串结,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舅列。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌割。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帐要,靈堂內的尸體忽然破棺而出把敞,到底是詐尸還是另有隱情,我是刑警寧澤榨惠,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布奋早,位于F島的核電站,受9級特大地震影響赠橙,放射性物質發(fā)生泄漏耽装。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一期揪、第九天 我趴在偏房一處隱蔽的房頂上張望掉奄。 院中可真熱鬧,春花似錦横侦、人聲如沸挥萌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽引瀑。三九已至,卻和暖如春榨馁,著一層夾襖步出監(jiān)牢的瞬間憨栽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屑柔,地道東北人屡萤。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像掸宛,于是被迫代替她去往敵國和親死陆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內容

  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品唧瘾,去做同樣的事情措译,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,771評論 2 17
  • 三饰序、閉包和高階函數(shù) 3.1 閉包 3.1.1 變量的作用域 所謂變量的作用域领虹,就是變量的有效范圍。通過作用域的劃分...
    梁同學de自言自語閱讀 1,454評論 0 6
  • 如何控制alert中的換行求豫?\n alert(“p\np”); 請編寫一個JavaScript函數(shù) parseQu...
    heyunqiang99閱讀 1,086評論 0 6
  • 就在我沖進衛(wèi)生間蹲在馬桶上止不住眼眶中咸味液體的前一刻塌衰,窗外綠葉之間還不時透下來蟬兒們滋滋的叫聲,我還窩在沙發(fā)之間...
    Nicoleqi閱讀 297評論 0 0
  • 本來還在琢磨,這周的心得寫什么是晨,忽然看到群里傳來溫馨提示肚菠,最后一周請于周五前提交本周心得。竟然最后一周了罩缴,瞬間被這...
    草渝田閱讀 444評論 1 3