如下是js經常使用到的一些設計模式總結。html
定義: 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點
。js的常見單例對象有線程池、全局緩存、瀏覽器中的window對象
。git
實現原理: 用一個變量來標誌是否已經爲某個類建立過實例對象,若是建立過,這在下一次獲取該類的實例時,直接返回以前建立的實例對象。github
優勢:ajax
單例模式的代碼實現以下:算法
class Singleton{
constructor(name){
this.name=name;
this.instance=null;
}
static getInstance(name){
if(!this.instance){
this.instance=new Singleton(name);
}
return this.instance;
}
}
// 測試
let a=Singleton.getInstance("test");
let b=Singleton.getInstance("test");
console.log(a===b);//true
複製代碼
惰性單例模式: 將建立對象和管理單例的邏輯分開。編程
//將管理單例的邏輯封裝成一個方法
const getSingle=function(){
let instance=null;
return function(){
return instance ||(instance=fn.apply(this,arguments))
}
}
//建立對象
const createLoginLayer=function(){
let div = document.createElement( 'div' );
div.innerHTML = '我是登陸浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
}
let createSingleLoginLayer = getSingle( createLoginLayer );
document.getElementById( 'loginBtn' ).onclick = function(){
let loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
複製代碼
定義: 定義一系列的算法,把它們各自封裝成策略,算法被封裝在策略內部。根據不一樣參數來命中不一樣的策略。設計模式
//定義策略
let strategies={
"S":salary=>salary*4,
"A":salary=>salary*3,
"B":salary=>salary*2,
"C":salary=>salary,
}
//調用策略
let calculateBonus=(level,salary)=> strategies[level](salary)
//測試
let sBonus=calculateBonus("S",40000);
let aBonus=calculateBonus("A",30000);
console.log(sBonus);//160000
console.log(aBonus);//90000
複製代碼
優勢數組
有效的避免多重條件選擇語句
。strategy
中,使得它們易於切換、易於理解,易於擴展。缺點瀏覽器
stratigy
。定義: 爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。代理模式的關鍵點:當用戶直接訪問一個對象或者不知足需求的時候,提供一個替身對象對這個對象的訪問。
緩存
代理模式分爲保護代理和虛擬代理。
一、虛擬代理: 將一些開銷很大的對象,延遲到真正須要它的纔去建立。
1)、用虛擬代理實現圖片預加載
思路: 先用一張loading圖片佔位,而後用異步的方式加載圖片,等圖片加載好了再將它填充到img節點裏。
代理的意義: 負責預加載圖片,預加載的操做完成後,把請求交給本體myImage。符合單一職責原則——(一個類或者一個對象和函數
),應該僅有一個引發它變化的緣由。
好處:
//功能:給img節點設置src
let myImage=(function(){
let imfNode=document.createElement("img");
document.body.appendChild(imgNode);
return function(src) {
imgNode.src=src;
}
})();
//功能:圖片預加載
let proxyImage=(function(){
//圖片預加載
let img=new Image();
img.onload=function(){
//真正的圖片加載成功時觸發,此時的圖片資源已經下載好了
myImage(this.src)
}
return function(src){
//添加默認圖片佔位
myImage("file:///E:/studyNotes/github/tangjie-93.github.io/images/git-branch.jpg")
img.src=src;
}
})();
proxyImage("http://pic44.nipic.com/20140723/18505720_094503373000_2.jpg");
複製代碼
2)、虛擬代理合並http請求
思路: 經過一個代理函數來收集一段時間內的請求,最後一次性的發送給服務器。
let synchronousFile=function(id){
console.log("開始同步文件,id爲:"+id);
}
let proxySynchronousFile=(function(){
let cache=[],timer;
return function(id){
if(timer){
return;
}
timer=setTimeout(()=>{
synchronousFile(cache.join(,));//
clearTimeout(timer);
timer=null;
cache.length=0;
},2000)
}
})()
document.getElementById("btn").onclick=function(){
proxySynchronousFile(id);
}
複製代碼
二、保護代理: 代理幫助本體過濾掉一些請求。
let Flower=function(){};
let xiaoming={
sendFlower:function(target){
target.receiveFlower();
}
}
//B屬於代理對象,能夠幫助A對象過濾一些請求
let B={
receiveFlower:function(){
//監聽A的好心情
A.listenGoodMood(()=>{
// new Flower()是一個大的開銷對象
let flower=new Flower();
A.receiveFlower(flower);
})
}
}
//目標對象
let A={
receiveFlower:function(flower){
console.log("收到花"+flower);
},
listenGoodMood:function(fn){
//延遲10秒
setTimeout(()=>{
fn();
},10000)
}
}
xiaoming.sendFlower(B);
複製代碼
三、緩存代理
緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟原來的一致,能夠直接返回以前存儲的運算結果。
實例:緩存乘積
let multi = function() {
let a = 1;
for (let i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
};
let add = function() {
let a = 1;
for (let i = 0, len = arguments.length; i < len; i++) {
a = a + arguments[i];
}
return a;
};
let proxyFactory = function(fn) {
let cache = new Map();
return function() {
let args = [].join.call(arguments, ",");
if (!cache.has(args)) {
cache.set(args, fn.apply(this, arguments));
}
return cache.get(args);
};
};
let proxyMulti=proxyFactory(multi);
proxyMulti(1, 2, 3, 4); // 輸出:24
proxyMulti(1, 2, 3, 4); // 輸出:24
let proxyAdd=proxyFactory(add);
proxyAdd(1, 2, 3, 4); // 輸出:10
proxyAdd(1, 2, 3, 4); // 輸出:10
複製代碼
定義: 指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。能夠分爲內部迭代器和外部迭代器。
好處: 能夠把迭代的過程從業務邏輯中分離出來,不用關心對象的內部構造,也能夠按順序訪問其中的每一個元素。
內部迭代器:函數內部已經定義好了迭代規則,外部只須要一次初始調用。
let each=function(args,callback){
for(let i=0,len=args.length;i<len;i++){
callback.call(args[i],i,args[i])
}
}
//測試
each([1,2,3],function(i,n){
console.log(i,n)
})
//判斷兩個數組元素裏的值是否徹底相等
let compare=function(arr1,arr2){
if(arr1.length!==arr2.length){
throw new Error("arr1和arr2不相等")
}
each(arr1,(i,n)=>{
if(n!==arr[i]){
throw new Error("arr1和arr2不相等")
}
})
alert("arr1和arr2相等")
}
//測試
compare([1,2,3],[1,2,3,4])// Uncaught Error: arr1和arr2不相等
複製代碼
外部迭代器:必須顯示的請求迭代下一個元素。增長了調用的複雜度,同時也加強了迭代器的靈活性,能夠手動控制迭代的過程或者順序。
let Iterator=function(obj){
let index=0;
let next=function(){
index+=1;
};
let isDone=function(){
return index>=obj.length;
};
let getCurItem=function(){
return obj[index];
};
return {
next,
isDone,
getCurItem
}
}
//判斷兩個數組元素裏的值是否徹底相等
let compare=function(iterator1,iterator2){
while(!iterator1.isDone()&&!iterator2.isDone()){
if(iterator1.getCurItem()!==iterator2.getCurItem()){
throw new Error ( 'iterator1 和 iterator2 不相等' );
}
iterator1.next();
iterator2.next();
}
alert ('iterator1 和 iterator2 相等');
}
var iterator1 = Iterator( [ 1, 2, 3 ] );
var iterator2 = Iterator( [ 1, 2, 3 ] );
compare( iterator1, iterator2 ); // iterator1 和 iterator2 相等
複製代碼
停止迭代器
let each=function(arr,callback){
for(let i=0;i<arr.length;i++){
if(callback(i,arr[i])===false){
break;
}
}
}
each([1,2,34,5,4,5],(i,n)=>{
if(n>5){
return false;
}
console.log(n);//輸出一、2
})
複製代碼
又稱爲訂閱者模式
,它定義了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。
缺點
優勢
時間上的解耦。
對象之間的解耦
實現步驟:
class EventBus{
constructor(){
//中介者
this.event=Object.create(null);
};
//註冊事件|監聽事件(訂閱者)
on(name,fn){
if(!this.event[name]){
//一個事件可能有多個監聽者
this.event[name]=[];
};
this.event[name].push(fn);
};
//觸發事件(觀察者)
emit(name,...args){
//給回調函數傳參
this.event[name]&&this.event[name].forEach(fn => {
fn(...args)
});
};
//只被觸發一次的事件
once(name,fn){
//在這裏同時完成了對該事件的註冊、對該事件的觸發,並在最後取消該事件。
const cb=(...args)=>{
//觸發
fn(...args);
//刪除該訂閱者
this.off(name,fn);
};
//監聽
this.on(name,cb);
};
//取消事件
off(name,offcb){
if(this.event[name]){
let index=this.event[name].findIndex((fn)=>{
return offcb===fn;
})
this.event[name].splice(index,1);
//沒有人訂閱該事件,則將該事件銷燬
if(!this.event[name].length){
delete this.event[name];
}
}
}
}
複製代碼
命令模式是最簡單和優雅的模式之一,命令模式中的命令
指的是一個執行某些特定事情的指令。
應用場景:須要向某些對象發送請求,可是並不知道請求的接收者是誰,也不知道請求的操做是什麼。核心就是將請求發送者和接收者解耦
。
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();
複製代碼
將對象組合成樹形結構,以表示"部分-總體"的層次結構。經過對象的多態性表現,使得用戶對單個對象和組合對象的使用具備一致性。
缺點
優勢
表示樹形結構:提供了一種遍歷樹形結構的方案,經過調用組合對象的execute方法,程序會遞歸調用組合對象下面的葉對象的execute方法。
利用對象多態性統一的對待組合對象和單個對象。對象的多態性表現,能夠忽略組合對象和單個對象的不一樣。在組合模式中,不須要關心是組合對象仍是單個對象。
組合模式使用場景
表示對象的部分-總體層次結構。
客戶但願統一對待樹中的全部對象。
let MacroCommand = function() {
return {
commandList: [],
add: function(command) {
this.commandList.push(command);
},
execute: function() {
for (let i = 0,command;command=this.commandList[i++];) {
command.execute();
}
}
};
};
var openAcCommand = {
execute: function() {
console.log("打開空調");
},
add:function(){
throw new Error( '葉對象不能添加子節點' );
}
};
var openTvCommand = {
execute: function() {
console.log("打開電視");
}
};
var openSoundCommand = {
execute: function() {
console.log("打開音響");
}
};
//組合命令1
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");
}
};
//組合命令2
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("btn").onclick = function() {
command.execute();
};
})(macroCommand);
複製代碼
利用組合模式掃描文件夾
class Folder {
constructor(name) {
this.name = name;
this.files = [];
this.parent=null;
};
add(file) {
file.parent=this;
this.files.push(file);
};
scan() {
console.log("開始掃描文件夾:"+this.name)
for (let i = 0, file; file = this.files[i++];) {
file.scan();
}
};
remove(){
if(!this.parent){
return;
}
for(let files=this.parent.files,len=files.length;len--;){
let file=files[len];
if(file===this){
files.splice(len,1);
}
}
}
}
//文件類
class File {
constructor(name) {
this.name = name;
this.parent=null;
};
add() {
throw new Error("文件下面不能再添加文件")
};
scan() {
console.log("開始掃描文件")
console.log("文件名爲:" + this.name)
};
remove(){
if(!this.parent){
return;
}
for(let files=this.parent.files,len=files.length;len--;){
let file=files[len];
if(file===this){
files.splice(len,1);
}
}
}
}
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);
folder1.remove();
folder.scan();
複製代碼
是一種只須要使用繼承就能夠實現的簡單模式。
模板方法模式由兩部分結構組成,第一部分是抽象類,第二部分是具體的實現子類。抽象父類中封裝子類的算法框架,包括公共方法和子類中全部方法的執行順序。子類經過繼承抽象類,來繼承整個算法結構,也能夠選擇重寫父類的方法。
提示: 不少時候都不須要依樣畫瓢的去實現一個模板方法模式,高階函數式更好的選擇。
class Beverage {
boilWater() {
console.log("把水煮沸");
};
brew() {
throw new Error("子類必須重寫brew方法");
};
pourInCup() {
throw new Error("子類必須重寫pourInCup 方法");
};
addCondiments() {
throw new Error("子類必須重寫 addCondiments 方法");
};
//鉤子函數
customerWantsCondiments() {
return true; // 默認須要調料
};
init() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.customerWantsCondiments()) {
// 若是掛鉤返回 true,則須要調料
this.addCondiments();
}
}
}
class CoffeeWithHook extends Beverage {
constructor() {
super();
};
boilWater() {
console.log("把水煮沸");
};
brew() {
console.log("用沸水沖泡咖啡");
};
pourInCup() {
console.log("把咖啡倒進杯子");
};
addCondiments() {
console.log('加糖和牛奶' );
};
customerWantsCondiments() {
return window.confirm("請問須要調料嗎?");
}
}
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();
複製代碼
享元(flyWeight)模式是一種用於性能優化的模式。其核心是運用共享技術
來有效支持大量細粒度的對象。享元模式要求把對象的屬性劃分爲內部狀態和外部狀態(狀態也就是屬性)。其目標是儘可能減小共享對象的數量。
內部狀態和外部狀態的劃分原則:
內部狀態存儲於對象內部。
內部狀態被一些對象共享。
內部狀態獨立於具體的場景,一般不會改變。
外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享。
享元模式的適用性
一個程序中使用了大量的類似對象。
因爲使用了大量對象,形成了很大的內存開銷。
對象的大多數狀態均可以變爲外部狀態。
剝離出對象的外部狀態以後,能夠用相對較少的共享對象取代大量對象。
一、享元模式之文件上傳
//建立共享對象(內部狀態)
class Upload {
constructor(uploadType) {
this.uploadType = uploadType;
};
delFile(id) {
if (this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
}
if (window.confirm("肯定要刪除該文件嗎? " + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom);
}
}
}
//建立上傳類工廠
class UploadFactory {
constructor() {
this.createdFlyWeightObjs = {};
}
//建立共享對象
create(uploadType) {
if (this.createdFlyWeightObjs[uploadType]) {
return this.createdFlyWeightObjs[uploadType];
}
this.createdFlyWeightObjs[uploadType] = new Upload(uploadType)
return this.createdFlyWeightObjs[uploadType] ;
}
}
//封裝外部狀態
class UploadManager {
constructor() {
this.uploadDatabase = {};
}
add(id, uploadType, fileName, fileSize) {
//建立享元對象
const uploadObj=new UploadFactory();
var flyWeightObj = uploadObj.create(uploadType);
const dom=this.addDivDom(id,fileName,fileSize,flyWeightObj);
this.uploadDatabase[id] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj;
};
addDivDom(id,fileName,fileSize,flyWeightObj){
let dom = document.createElement("div");
dom.innerHTML = `<span>文件名稱: ${fileName}, 文件大小:${fileSize} </span><button class="delFile">刪除</button>`;
dom.querySelector(".delFile").onclick = ()=>{
this.setExternalState(id, flyWeightObj);
flyWeightObj.delFile(id);
};
document.body.appendChild(dom);
return dom;
};
//設置外部狀態
setExternalState(id, flyWeightObj) {
var uploadData = this.uploadDatabase[id];
Object.keys(uploadData).forEach(key=>{
flyWeightObj[key] = uploadData[key];
})
}
}
let id = 0;
const startUpload = (uploadType, files) => {
const uploadManager=new UploadManager();
for (var i = 0, file; (file = files[i++]); ) {
var uploadObj = uploadManager.add(
++id,
uploadType,
file.fileName,
file.fileSize
);
}
};
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 }
]);
//有多少種內部狀態的組合,就有多少個共享對象。
複製代碼
二、對象池
對象池也是一種性能優化方案,跟享元模式有一些類似之處,可是沒有分離內部狀態和外部狀態。
class ObjectPoolFactory {
constructor(createObjFn) {
this.objectPool = [];
}
create(createObjFn) {
let obj =
this.objectPool.length === 0
? createObjFn.apply(this, arguments)
: this.objectPool.shift();
return obj;
};//回收節點
recover(obj) {
this.objectPool.push(obj);
}
}
function createIframe() {
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
iframe.onload = function() {
iframe.onload = null;
// 防止 iframe 重複加載的 bug
new ObjectPoolFactory().recover(iframe);
// iframe 加載完成以後回收節點
};
return iframe;
}
let ObjectPool= new ObjectPoolFactory();
let iframe1=ObjectPool.create(createIframe);
iframe1.src='http://baidu.com';
let iframe2=ObjectPool.create(createIframe);
iframe2.src='http://QQ.com';
let iframe3=ObjectPool.create(createIframe);
iframe3.src='http://QQ.com';
複製代碼
定義: 使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連城一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
優勢:
缺點:
職責鏈模式的經常使用場景: 早高峯坐公交投幣。(將硬幣往前傳遞給售票員)
實例:電商網站
class Chain{
constructor(fn) {
this.fn=fn;
this.successor =null;
};
setNextSuccessor(successor ){
//return 供鏈式調用
return this.successor =successor
}
passRequest(){
//判斷執行結果是否是nextSuccessor,是的話,繼續往下執行
const result=this.fn.apply(this,arguments);
if(result==="nextSuccessor"){
//遞歸,直到val不等於nextSuccessor爲止
return this.successor&& this.successor.passRequest.apply(this.successor,arguments)
}
return val;
}
}
const order500=(orderType,pay,stock)=>{
if(orderType==1&&pay===true){
console.log("500 元定金預購, 獲得 100 優惠券");
return;
}else{
return 'nextSuccessor'
}
}
const order200=(orderType,pay,stock)=>{
if(orderType==2&&pay===true){
console.log("200 元定金預購, 獲得 50 優惠券");
return;
}else{
return 'nextSuccessor'
}
}
const orderNormal =(orderType,pay,stock)=>{
if(stock>0){
console.log( '普通購買,無優惠券' );
}else{
console.log( '手機庫存不足' );
}
}
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
chainOrder500.setNextSuccessor( 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實現職責鏈
const order500=(orderType,pay,stock)=>{
if(orderType==1&&pay===true){
console.log("500 元定金預購, 獲得 100 優惠券");
return;
}else{
return 'nextSuccessor'
}
}
const order200=(orderType,pay,stock)=>{
if(orderType==2&&pay===true){
console.log("200 元定金預購, 獲得 50 優惠券");
return;
}else{
return 'nextSuccessor'
}
}
const orderNormal =(orderType,pay,stock)=>{
if(stock>0){
console.log( '普通購買,無優惠券' );
}else{
console.log( '手機庫存不足' );
}
}
//切換編程
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 = order500.after( order200 ).after( orderNormal );
order( 1, true, 500 ); // 輸出:500 元定金預購,獲得 100 優惠券
order( 2, true, 500 ); // 輸出:200 元定金預購,獲得 50 優惠券
order( 1, false, 500 ); // 輸出:普通購買,無優惠券
複製代碼
中介者模式的做用就是解除對象和對象之間的緊耦合關係。
優勢: 以中介者和對象的一對多關係取代了對象之間的網狀多對多關係。每一個對象只須要關注自身功能的實現便可。對象之間的交互關係交給中介者來實現和維護。
缺點: 系統中會增長一箇中介者對象,對象之間的交互複雜性,轉移成了中介者對象的複雜性,使得中介者對象自身會成爲一個難以維護的對象。
實例:用中介者模式實現泡泡堂遊戲
class Player {
constructor(name, teamColor) {
this.name =name;
this.teamColor = teamColor;
this.state = "alive";
this.add();
};
win() {
console.log(`玩家${this.name}贏了`);
};
lose() {
console.log(`玩家${this.name}輸了`);
};
add(){
if(!Player.playDirector){
Player.playDirector=new PlayDirector();
}
Player.playDirector.addPlayer(this);
};
die() {
this.state = "dead";
// 給中介者發送消息,玩家死亡
Player.playDirector.playerDead(this);
};
remove() {
console.log(`玩家${this.name}掉線了`);
Player.playDirector.removePlayer(this);
};
changeTeam(color) {
console.log(`玩家${this.name}叛變了`);
// 給中介者發送消息,玩家換隊
Player.playDirector.changeTeam(this, color);
}
}
Player.playDirector=null;
class PlayDirector {
constructor() {
this.players = {};
}
addPlayer(player) {
let teamColor = player.teamColor; //玩家額隊伍顏色
this.players[teamColor] = this.players[teamColor] || [];
this.players[teamColor].push(player); //添加玩家
}
//移除玩家
removePlayer(player) {
let teamColor = player.teamColor;
let teamPlayers = this.players[teamColor] || [];
const index = teamPlayers.indexOf(player);
index>-1 && teamPlayers.splice(index, 1);
}
//玩家換隊
changeTeam(player, newTeamColor) {
this.removePlayer(player); // 從原隊伍中刪除
player.teamColor = newTeamColor; // 改變隊伍顏色 operations.addPlayer( player ); // 增長到新隊伍中
}
//玩家死亡
playerDead(player) {
// 玩家死亡
var teamColor = player.teamColor,
teamPlayers = this.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
}
console.log(teamColor+"隊輸了")
for (var color in this.players) {
if (color !== teamColor) {
var teamPlayers = this.players[color]; // 其餘隊伍的玩家
for (var i = 0, player; (player = teamPlayers[i++]); ) {
player.win(); // 其餘隊伍全部玩家 win
}
}
}
}
}
}
//測試
// 紅隊:
var player1 = new Player( '皮蛋', 'red' ),
player2 = new Player( '小乖', 'red' ),
player3 = new Player( '寶寶', 'red' ),
player4 =new Player( '小強', 'red' );
// 藍隊:
var player5 = new Player( '黑妞', 'blue' ),
player6 = new Player( '蔥頭', 'blue' ),
player7 = new Player( '胖墩', 'blue' ),
player8 = new Player( '海盜', 'blue' );
//紅隊所有死亡
// player1.die();
// player2.die();
// player3.die();
// player4.die();
//玩家 player1和player1掉線
// player1.remove();
// player2.remove();
// player3.die();
// player4.die();
//玩家1叛變
player1.changeTeam( 'blue' );
player2.die();
player3.die();
player4.die();
複製代碼
給對象動態的增長職責的方式稱爲裝飾者模式
。裝飾者模式可以在不改變對象自身的基礎上,在程序運行期間給對象動態的添加職責。是一種即用即付
的方式。
裝飾者模式將一個對象嵌入到另外一個對象中,實際上至關於這個對象被另外一個對象包裝起來,造成一條包裝鏈。請求隨着這條鏈依次傳遞到全部的對象,每一個對象都有處理這條請求的機會。
一、模擬傳統面嚮對象語言的裝飾者模式
分析:給對象動態增長職責的方式,並無真正地改動對象自身,而是將對象放入另外一個對象 之中,這些對象以一條鏈的方式進行引用,造成一個聚合對象。這些對象都擁有相同的接口(fire 方法),當請求達到鏈中的某個對象時,這個對象會執行自身的操做,隨後把請求轉發給鏈中的 下一個對象。
var Plane = function(){}
//
Plane.prototype.fire = function(){
console.log( '發射普通子彈' );
}
//接下來增長兩個裝飾類,分別是導彈和原子彈:
var MissileDecorator = function( plane ){
this.plane = plane;
}
MissileDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '發射導彈' );
}
//將一個對象放入另外一個對象中
var AtomDecorator = function( plane ){
this.plane = plane;
}
//給對象動態添加職責
AtomDecorator.prototype.fire = function(){
this.plane.fire();
console.log( '發射原子彈' );
}
var plane = new Plane();
plane = new MissileDecorator( plane );
plane = new AtomDecorator( plane );
plane.fire();
複製代碼
二、裝飾函數
var a = function(){
alert (1);
}
var _a = a;
a = function(){
_a();
alert (2);
}
a();
複製代碼
裝飾函數的優勢:
將行爲依照職責分紅粒度更細的函數,隨後經過裝飾把它們合併到一塊兒,這有助於我 們編寫一個鬆耦合和高複用性的系統。
裝飾函數缺點:
必須維護中間變量,若是函數的裝飾鏈較長,或者 須要裝飾的函數變多,這些中間變量的數量也會愈來愈多。
this 被劫持的問題。
var _getElementById = document.getElementById;
document.getElementById = function( id ){
alert (1);// (1)
return _getElementById( id ); // 輸出: Uncaught TypeError: Illegal invocation
}
//document.getElementById方法的內部實現須要 使用 this 引用,this 在這個方法內預期是指向 document,而不是 window,調用_getElementById( id )方法時內部的this指向的是window。
複製代碼
三、用AOP裝飾函數(給函數動態添加功能)
Function.prototype.before = function( beforefn ){
var __self = this; // 保存原函數的引用
return function(){ // 返回包含了原函數和新函數的"代理"函數
beforefn.apply( this, arguments ); // 執行新函數,且保證 this 不被劫持,新函數接受的參數,
//也會被原封不動地傳入原函數,新函數在原函數以前執行(前置裝飾) ,這樣就實現了動態裝飾的效果
return __self.apply( this, arguments ); // 執行原函數並返回原函數的執行結果,而且保證 this 不被劫持
}
}
//不污染原型的寫法
var before = function( fn, beforefn ){
return function(){
beforefn.apply( this, arguments );
return fn.apply( this, arguments );
}
}
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
}
//不污染原型的寫法
var after = function( fn, beforefn ){
return function(){
var ret = fn.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
}
//測試
var a = before(
function () { console.log(3) },
function () { console.log(2) }
);
a = before(a, function () {
console.log(1);
});
a();//順序輸出1,2,3
複製代碼
三、1 AOP的應用實例
裝飾行爲依照職責分紅粒度更細的函數,隨後經過裝飾把它們合併到一塊兒,這有助於我 們編寫一個鬆耦合和高複用性的系統。
數據統計上報
分離業務代碼和數據統計代碼。
//業務代碼
var showLogin = function(){
console.log( '打開登陸浮層' );
}
var log = function(){
console.log( '上報標籤爲: ' + this.getAttribute( 'tag' ) );
}
//數據統計代碼
showLogin = showLogin.after( log ); // 打開登陸浮層以後上報數據
document.getElementById( 'button' ).onclick = showLogin;
複製代碼
用AOP動態改變函數的參數
var func = function( param ){
console.log( param ); // 輸出: {a: "a", b: "b"}
}
func = func.before( function( param ){
param.b = 'b';
});
//在調用func以前調用before
func( {a: 'a'} );
複製代碼
插件式的表單驗證
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();
}
複製代碼
3.2 用AOP裝飾函數的缺點
經過 Function.prototype.before 或者 Function.prototype.after 被裝 飾以後,返回的其實是一個新的函數,若是在原函數上保存了一些屬性,那麼這些屬性會丟失。由於原函數所指向的內存地址發生了變化,原函數指向了另外一個函數。
var func = function () { alert(1); }
func.a = 'a';
func = func.after(function () { alert(2); });
alert(func.a); // 輸出:undefined
複製代碼
這種裝飾方式也疊加了函數的做用域,若是裝飾的鏈條過長,性能上也會受到一些 影響。
四、裝飾者模式和代理模式的區別
相同點: 都描述了怎樣爲對象提供必定程度上的間接引用。它們的實現部分都保留了對另外一個對象的引用(返回一個函數
),而且向那個對象發送請求(調用返回的函數
)。
區別: 最主要的區別在於設計目的和意圖。
代理模式強調的是一種在一開始就肯定的靜態關係
。代理模式一般只有一層代理-本體
的引用。 定義: 容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。
狀態模式的關鍵是區分事物的內部狀態
,由於事物內部狀態的改變每每會引發事物行爲的改變。
狀態模式的關鍵是把事物的每種狀態都封裝成單獨的類
,跟此類有關的行爲都被封裝在這個類的內部。
狀態模式的通用結構
class State{
constructor(light){
this.light=light;
};
buttonPress(state){
// this.light.setState( this.light.offLightState );
this.light.setState( state );
}
}
class OffLightState extends State{
constructor(){
super(this);
};
}
class WeakLightState{
constructor(light){
super(light);
}
}
class StrongLightState{
constructor(light){
super(light);
}
}
class SuperStrongLightState{
constructor(light){
super(light);
}
}
class Light{
constructor(){
this.offLightState = new OffLightState( this ); // 持有狀態對象的引用 // 將對象保存爲對象的屬性。
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.superStrongLightState = new SuperStrongLightState( this );
this.button = null;
};
init(){
this.currentState=this.offLightState;
this.button=document.createElement("button");
this.button.innerHTML="開關";
this.button.onclick=()=>{
this.currentState.buttonPress();
}
};
setState(newState){
this.currState = newState;
}
}
var light = new Light();
light.init();
複製代碼
狀態模式的優缺點
缺點: 會在系統中定義許多狀態類。
優勢
狀態模式和策略模式的關係
區別:策略模式
中的各個策略類之間是平等又平行的,它們之間沒有任何聯繫, 因此客戶必須熟知這些策略類的做用,以便客戶能夠隨時主動切換算法。在狀態模式
中,狀態 和狀態對應的行爲是早已被封裝好的,狀態之間的切換也早被規定完成,「改變行爲」這件事情 發生在狀態模式內部。
相同點: 都有一個上下文、一些策略或者狀態類,上下文把請 求委託給這些類來執行。
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));
複製代碼
適配器模式、裝飾者模式、代理模式和外觀模式的區別
相同點: 都屬於包裝模式
。都是由一個對象來包裝另外一個對象。
區別:
適配器模式主要是用來解決兩個接口之間不匹配的問題。不考慮這些接口是怎樣實現的,也不考慮這些接口未來怎麼變化。
裝飾者模式和代理模式也不會改變原有對象的接口,但裝飾者模式的做用是爲了給對象增長功能。裝飾者模式經常造成一條長的裝飾鏈,而適配器模式一般只包裝一次。代理模式是爲了控制對對象的訪問,一般也只包裝一次。
外觀模式的做用和適配器比較類似,有人把外觀模式當作一組對象的適配器,但外觀模式顯著的特色是定義了一個新的接口。
體現爲一個對象(方法)只作一件事情。
優勢: 下降了單個類或對象的複雜度,按照職責將對象分解爲更小的粒度,這有助於代碼的複用,同時也有助於單元測試。這樣當一個職責變動的時候,不會影響到其餘的功能。下降了代碼耦合度。
不足: 會增長編寫代碼的複雜度。將對象按照職責分解成更小粒度後,同時也增長了這些對象互相聯繫的難度。
設計模式中有用到第一職責原則的有單例模式、代理模式、迭代器模式、裝飾者模式
。
指的是一個對象儘可能減小與其餘對象之間發生相互做用。
設計模式中用到最少知識原則的是中介者模式、外觀模式、
。封裝也是最少知識原則的一種體現。
當須要改變一個程序的功能或者說是要給該程序增長新功能時,可使用增長代碼的方式,可是不要不容許更改程序的原代碼。
AOP動態裝飾函數就很好的運用到了開放閉合原則。
開放閉合原則最重的就是把程序中變化的部分找出並封裝起來,將程序中不變和變化的部分隔離開來。
實行開放封閉原則的常見方法有:
利用對象的多態性。
放置掛鉤。在程序有可能發生變化的地方放置一個掛鉤,掛鉤的返回結果決定了程序的下一步走向
使用回調函數。以把一部分易於變化的邏輯封裝在回調函數裏,而後把回調函數看成參數傳入一個穩定和封閉的函數中。當回調函數被執行的時候,程序就能夠由於回調函數的內部邏輯不一樣,而產生不一樣的結果
設計模式中用到開放閉合原則的主要有發佈訂閱模式、模板方法模式、策略模式、代理模式、職責鏈模式
。
模式和重構有着一種與生俱來的關係,設計模式的行爲的目標就是爲了代碼重構作準備。
代碼重構的主要手段
提煉函數。
合併重複的條件片斷。
把條件分支語句提煉成函數。
var isSummer = function(){
var date = new Date();
return date.getMonth() >= 6 && date.getMonth() <= 9;
};
var getPrice = function( price ){
if ( isSummer() ){ // 夏天
return price * 0.8;
}
return price;
};
複製代碼
合理使用循環。
var createXHR = function(){
var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
for ( var i = 0, version; version = versions[ i++ ]; ){
try{
return new ActiveXObject( version );
}catch(e){
}
}
};
var xhr = createXHR();
複製代碼
提早讓函數退出代替嵌套條件分支 。
var del = function( obj ){
if ( obj.isReadOnly ){
// 反轉 if 表達式
return;
}
if ( obj.isFolder ){
return deleteFolder( obj );
}
if ( obj.isFile ){
return deleteFile( obj );
}
};
複製代碼
傳遞對象參數代替過長的參數列表
var setUserInfo = function( obj ){
console.log( 'id= ' + obj.id );
console.log( 'name= ' + obj.name );
console.log( 'address= ' + obj.address );
console.log( 'sex= ' + obj.sex );
console.log( 'mobile= ' + obj.mobile );
console.log( 'qq= ' + obj.qq );
};
setUserInfo({ id: 1314, name: 'sven', address: 'shenzhen', sex: 'male', mobile: '137********', qq: 377876679 });
複製代碼
儘可能減小參數數量
少用三目運算符
合理使用鏈式調
分解大型類
用return 退出多重循環
var func = function(){
for ( var i = 0; i < 10; i++ ){
for ( var j = 0; j < 10; j++ ){
if ( i * j >30 ){
//避免有代碼沒有被執行
return print( i );
}
}
}
};
複製代碼