javascript設計模式

設計模式

1、準備知識:call、apply、閉包、高階函數

1. call、apply:改變this指向

2. 閉包:函數做用域嵌套,內層做用域能夠訪問外層做用域

利用閉包模擬bind:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	Function.prototype.bind = function () {
		var self = this;
		var context = [].shift.call(arguments);
		var args = [].slice.call(arguments);

		return function () {
			return self.apply(context, [].concat.apply(args, [].slice.call(arguments)));
		};
	};

	function say (initValue1, initValue2, word) {
		console.log(this);
		console.log(initValue1);
		console.log(initValue2);
		console.log(word);
	}

	say.bind({name: 1}, "initValue1", "initValue2")("bind");
</script>
</body>
</html>

閉包造成過程:

閉包造成過程

3. 高階函數:函數做爲參數傳遞或者函數做爲返回值返回(如回調函數和閉包返回的函數)

利用閉包和高階函數重寫類型判斷

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script type="text/javascript">
		var toString = Object.prototype.toString;
		var Type = {};

		function isNumber(value) {
			return toString.call(value) === "[object Number]";
		}

		function isString(value) {
			return toString.call(value) === "[object String]";
		}

		function isArray(value) {
			return toString.call(value) === "[object Array]";
		}

		function isFunction(value) {
			return toString.call(value) === "[object Function]";
		}

		function isTypeFactory(type) {
			return function (value) {
				return Object.prototype.toString.call(value).toUpperCase() === ("[object " + type + "]").toUpperCase();
			};
		}

		for (var i = 0, types = ['Number', 'String', 'Array', 'Function'], type; type = types[i++];) {
			Type['is' + type] = isTypeFactory(type);
		}
	</script>
</body>
</html>

2、工廠模式

定義、提供一個通用的接口來批量生產一類類似對象,咱們能夠指定咱們所但願的工廠對象的類型

1. 簡單工廠

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	function Button(name) {
		this.dom = document.createElement("button");
		this.name = name;
	}

	function Input(name) {
		this.dom = document.createElement("input");
		this.name = name;
	}

	function UIFactory() {
		
	}

	UIFactory.prototype.createUI = function () {
		var type = [].shift.call(arguments);
		var UIClass = null, ui;

		function UI() {

		}

		switch(type) {
			case "Button":
				UIClass = Button;
				break;
			case "Input":
				UIClass = Input;
				break;
			default : null;
		}

		if (!UIClass) {
			return {};
		}
		
		UI.prototype = UIClass.prototype;
		ui = new UI;
		UIClass.apply(ui, arguments);
		return obj;
	}

	var uiFactory = new UIFactory();
	var button = uiFactory.createUI("Button", "button");
	var input = uiFactory.createUI("Input", "input");
</script>
</body>
</html>

2. 抽象工廠(一組具備共同目的的單個工廠)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="text" id="pos">
<button id="excute">excute</button>
<button id="uodo">uodo</button>
<select id="direction">
    <option value="top">top</option>
    <option value="bottom">bottom</option>
    <option value="left" selected>left</option>
    <option value="right">right</option>
</select>
<div id="ball"></div>
<script type="text/javascript">
	function Animal () {

	}

	function People (type, name, age) {
		this.type = type;
		this.name = name;
		this.age = age;
	}

	People.prototype = new Animal();
	People.prototype.constructor = People;
	People.prototype.run = function () {
		console.log(this.name + "run");
	};

	function PeopleFactory (type, name, age) {
		var people = new People(type, name, age);

		people.height = "175cm";
		return people;
	}

	function Bird (type, color) {
		this.type = type;
		this.color = color;
	}

	Bird.prototype = new Animal();
	Bird.prototype.constructor = Bird;
	Bird.prototype.fly = function () {
		console.log(this.type + "fly");
	};

	function BirdFactory (type, color) {
		return new Bird(type, color);
	}

	var AbstractAnimalFactory = (function () {
		var types = {};

		function registerFactory (type, AnimalFactory) {
			if (Animal.prototype.constructor !== Animal) {
				return AbstractAnimalFactory;
			}

			types[type] = AnimalFactory;
			return AbstractAnimalFactory;
		}

		function createAnimal () {
			var animalType = [].shift.call(arguments);
			var AnimalType = types[animalType];
			var animal = AnimalType.apply({}, arguments);

			return animal || null;
		}

		return {
			registerFactory: registerFactory,
			createAnimal: createAnimal
		};
	})();

	AbstractAnimalFactory.registerFactory("people", PeopleFactory);
	AbstractAnimalFactory.registerFactory("bird", BirdFactory);

	var kaka = AbstractAnimalFactory.createAnimal("people", "male", "kaka", "32");
	var bird1 = AbstractAnimalFactory.createAnimal("bird", "xiao", "red");
</script>
</body>
</html>

3、單例模式

定義:保證一個類只有一個實例,並提供一個訪問它的全局訪問點。

1. 普通單例模式

缺點:閱讀不舒服,增長程序複雜性,並且構造函數負責了多項職責(建立對象和初始化,保證只有一個對象),違背單一職責原則
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	var Single = (function () {
	    var single = null;

	    var Animal = function (name, age) {

	      if (single) {
	            return single;
	        }
	        this.name = name;
	        this.age = age;

	        this.init();
	       return  single = this;
	    }

	    Animal .prototype.init = function () {};

	    return Animal;
	})();

	var obj = new Single("curry", 29);
	var obj1 = new Single("kaka", 35);

	console.log(obj === obj1); //true
</script>
</body>
</html>

2. 利用代理實現單例模式(須要的時候才建立單例,而不是初始化時建立單例)

優勢:將普通類與單例的生成邏輯隔離開,實現了單一職責原則,普通類能夠正常建立普通類的對象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    var ProxySingleton = (function () {
        var singleton = {};
        var classNameReg = /^function\s*(\w+)\s*\(/;

        return function () {
            var Class = [].shift.call(arguments);
            var classStr = Class.toString();
            var className = classNameReg.exec(classStr)[1];
            var args = arguments;

            function  SingletonClass () {
                Class.apply(this, args);
            }

            SingletonClass.prototype = Class.prototype;

            if (singleton[className]) {
                return singleton[className];
            }

            return singleton[className] = new SingletonClass();
        }
    })();

    function Animal (type, name) {
        this.type = type;
        this.name = name;
    }

    var dog = ProxySingleton(Animal, "dog", "dog-01");
    var cat = ProxySingleton(Animal, "cat", "cat-01");
    console.log(dog === cat); //true
</script>
</body>
</html>

3. 惰性單例(須要的時候才建立單例,而不是初始化時建立單例)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	var getSingle = function (fn) {
	    var result;
	    return function (){
	        return result || (result = fn.apply(this, arguments));
	    };

	};
	var createLoginLayer = function () {
	    var div = document.createElement("div");
	    div.innerHTML = "Login";
	    return div;
	};

	var createSingleLoginLayer = getSingle(createLoginLayer );
	var loginLayer = createSingleLoginLayer();
</script>
</body>
</html>

4、策略模式

定義:定義一系列的算法,把他們一個個封裝起來,而且使他們能夠相互替換。

1. 計算獎金例子

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    var strategies = {
	    S: function (salary) {return return  * 4;},
	    A: function (salary) {return return  * 3;},
	    B: function (salary) {return return  * 2;}
    };
    var calculateBonus = function (level, salary) {
        return strategies [level](salary);
    };

    calculateBonus ("S", 20000); //80000
    calculateBonus ("A",10000); //30000
</script>
</body>
</html>

2. 策略模式實現表單驗證

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	const strategies = {};

	strategies.notEmpty = (value, errMsg, validateField) => {
	  if (!value) {
	    return {
	      errMsg: errMsg,
	      validateField: validateField
	    };
	  }
	};
	strategies.isNumber = (value, errMsg, validateField) => {
	  try {
	    if (!String.prototype.match.call(value, /^\d+$/)) {
	      return {
	        errMsg: errMsg,
	        validateField: validateField
	      };
	    }
	  } catch(err) {
	    return;
	  }
	};

	function Validator() {
	    this.cache = [];
	    this.validateRes = null;
	    this.timer = null;
	}

	Validator.prototype.add = function (value, rule, errMsg, validateField) {
		this.cache.push(function () {
			return strategies[rule].apply(strategies, [value, errMsg, validateField]);
		});
	};
	Validator.prototype.validate = function (isAll) {
	    let cache = this.cache;
	    let validateResArr = [];

	    this.validateRes = null;

	    if (!isAll) {
	      for (let i = 0, validateFucn; validateFucn = cache[i++];) {
	        let validateRes = validateFucn();

	        if (validateRes) {
	          return this.validateRes = validateRes;
	        }
	      }

	      return null;
	    }

	    cache.forEach(function (validateFucn) {
	      let validateRes = validateFucn();

	      if (validateRes) {
	        validateResArr.push(validateRes);
	      }
	    });

	    return this.validateRes = validateResArr;
	};

	var validator = new Validator();

	validator.add("", "notEmpty", "名字必須非空", "name");
	validator.add("fefr", "isNumber", "手機必須由數字組成", "phone");
</script>
</body>
</html>

5、代理模式(通常代理對象要實現本體對象全部的接口)

定義:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。

一、小明送花例子(經過B將花送給A)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    function Flower () {

    }
    var xiaoming = {
        sendFlower: function (target) {
            target.receiveFlower(new Flower());
        }
    };
    var B = {
        receiveFlower: function (flower) {
            A.listenGoodMood(function () {
                A.receiveFlower(flower);
            });
        }
    };
    var A = {
        receiveFlower: function (flower) {
            console.log("收到花" + flower);
        },
        listenGoodMood: function (fn) {
            setTimeout(function () {
                fn();
            }, 5000);
        }
    };

    xiaoming.sendFlower(B);
</script>
</body>
</html>

2.虛擬代理實圖片預加載(虛擬代理將一些開銷很大的對象,延遲到真正須要它的時候才起建立)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    var myImage = (function () {
        var imgNode = document.createElement("img");
        document.body.appendChild(imgNode);
        return {
            setSrc: function (src) {
                imgNode.src = src;
            }
        }
    })();
    var proxyImage = (function () {
        var img = document.createElement("img");
        img.onload = function () {
        	var self = this;
            setTimeout(function () {
            	myImage.setSrc(self.src);
            }, 1500);
        };
        return {
            setSrc: function (src) {
                myImage.setSrc("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502270518122&di=b3dcb328920e8feef3c1fbb4ed00ea9c&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01a0d456052ebc32f875a132c28f69.gif");
                img.src = src;
            }
        }
    })();

    proxyImage.setSrc("http://tupian.enterdesk.com/2013/xll/009/07/7/1.jpg");
</script>
</body>
</html>

三、虛擬代理合並HTTP請求

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<input type="checkbox" id="1">
<input type="checkbox" id="2">
<input type="checkbox" id="3">
<input type="checkbox" id="4">
<input type="checkbox" id="5">
<script type="text/javascript">
    function syncFile(id) {
        console.log("開始同步文件,id爲:" + id);
    }
    var proxySyncFile = (function () {
        var cache = [], timer;

        return function (id) {
            cache.push(id);

            if (timer) {
                return;
            }

            timer = setTimeout(function () { //三秒後向本體發送須要同步的ID集合
                syncFile(cache.join(", "));
                clearTimeout(timer);
                timer = null;
                cache.length = 0;
            }, 3000);
        }
    })();

    var checkbox = document.getElementsByTagName("input");

    for (var i = 0; c = checkbox[i++];) {
        c.onclick = function () {
            if (this.checked) {
                proxySyncFile(this.id);
            }
        }
    }
</script>
</body>
</html>

三、緩存代理

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    function multi () {
        var value = 1;

        for (var i = 0, l = arguments.length; i < l; i++) {
            value *= arguments[i];
        }

        return value;
    }

    var proxyMulti = (function () {
        var cache = {};

        return function () {
            var args = [].join.call(arguments, ",");

            if (cache[args]) {
                return cache[args];
            }

            return cache[args] = multi.apply(this, arguments);
        };
    })();

    proxyMulti(1, 2, 3);
    proxyMulti(1, 2, 3);
</script>
</body>
</html>

6、迭代器模式

定義:提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。

一、內部迭代器(each函數內部已經定義好迭代規則)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    function each (arr, callback) {
        for (var i = 0, l = arr.length; i < l; i++) {
           if (callback.call(arr[i], i, arr[i]) === false) {
                return false;    
            }
        }
    }

    each([1, 2, 3], function (i, n) {
        console.log(i, n);
    });
</script>
</body>
</html>

二、外部迭代器(必須顯示的請求迭代下一個元素,加強了迭代的靈活性,能夠手動控制迭代過程或者順序)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    function Iterater (obj) {
        var current = 0;
        function next () {
            current += 1;
        }
        function isDone () {
            return current >= obj.length;
        }
        function getCurrentItem () {
            return obj[current];
        }
        function getLength() {
           return obj.length;
        }

        return {
            next: next,
            isDone: isDone,
            getCurrentItem: getCurrentItem,
            getLength: getLength
        };
    }

    function compare (iterater1, iterater2) {
        if  (iterater1.length !==iterater2.length ) {
            throw new Error("iterater1和iterater2不相等");
        }

        while (!iterater1.isDone() || !iterater2.isDone()) {
            if (iterater1.getCurrentItem() !== iterater2.getCurrentItem()) {
                throw new Error("iterater1和iterater2不相等");
            }
            
            iterater1.next();
            iterater2.next();
        }

        console.log("iterater1和iterater2相等");
    }

    var iterater1 = Iterater([1, 2, 3]);
    var iterater2 = Iterater([1, 2, 3]);

    compare(iterater1, iterater2);
</script>
</body>
</html>

7、發佈-訂閱模式(觀察者模式)

定義:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部倚賴於它的對象都將獲得通知。咱們通常用事件模型來替代傳統的發佈-訂閱模式。

一、簡單例子

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
	var event = {
        clientList: {},
        listen: function (key, callback) {
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }

            this.clientList[key].push(callback)
        },
        remove: function (key, callback) {
            var callbacks = this.clientList[key];

            if (!callbacks) {
                return false;
            }

            if (!callback) { //取消key對應消息的全部訂閱
                callbacks.length = 0;
            } else {
                for (var i = callbacks.length - 1; i >= 0; i--) {
                    if (callbacks[i] === callback) {
                        callbacks.splice(i, 1);
                    }
                }
            }
        },
        trigger: function () {
            var key = Array.prototype.shift.call(arguments);
            var callbacks = this.clientList[key];
            
            if (!callbacks || callbacks.length === 0) {
                return false;
            }

            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i].apply(this, arguments);
            }
        }
    };

    function installEvent (obj) {
        for (var i in event) {
            obj[i] = event[i];
        }
    }

    var salesOffices = {};

    installEvent(salesOffices);

    salesOffices.listen("squareMeter88", function (price) {
        console.log("價格= " + price);
    });

    salesOffices.listen("squareMeter100", function (price) {
        console.log("價格= " + price);
    });

    salesOffices.trigger("squareMeter88", 1000000);
    salesOffices.trigger("squareMeter100", 1300000);
</script>
</body>
</html>

二、Event做爲中介者的角色,把訂閱者和發佈者聯繫起來

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
    var Event = {
        clientList: {},
        listen: function (key, callback) {
            if (!this.clientList[key]) {
                this.clientList[key] = [];
            }

            this.clientList[key].push(callback)
        },
        remove: function (key, callback) {
            var callbacks = this.clientList[key];

            if (!callbacks) {
                return false;
            }

            if (!callback) { //取消key對應消息的全部訂閱
                callbacks.length = 0;
            } else {
                for (var i = callbacks.length - 1; i >= 0; i--) {
                    if (callbacks[i] === callback) {
                        callbacks.splice(i, 1);
                    }
                }
            }
        },
        trigger: function () {
            var key = Array.prototype.shift.call(arguments);
            var callbacks = this.clientList[key];
            
            if (!callbacks || callbacks.length === 0) {
                return false;
            }

            for (var i = 0, len = callbacks.length; i < len; i++) {
                callbacks[i].apply(this, arguments);
            }
        }
    };

    Event.listen("squareMeter88", function (price) {
        console.log("價格= " + price);
    });

    Event.listen("squareMeter100", function (price) {
        console.log("價格= " + price);
    });

    Event.trigger("squareMeter88", 1000000);
    Event.trigger("squareMeter100", 1300000);
</script>
</body>
</html>

8、命令模式(解耦命令請求者與命令接受者之間的強耦合關係,當咱們不知道命令接受者是誰,以及被請求的操做是什麼時,適合用命令模式;命令模式中的命令指的是一個執行某些特定事情的指令)

一、閉包實現模式

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="refresh">refresh</button>
<script type="text/javascript">
    function setCommand (btn, command) {
        btn.onclick = function () {
            command.excute();
        };
    };
    var Menubar = {
        refresh: function () {
            console.log("刷新菜單界面");
        }
    };
    function RefreshMenubarCommand (receiver) {
        return {
            excute: function () {
                receiver.refresh();
            }
        };
    };

    var refreshMenubarCommand = RefreshMenubarCommand(Menubar);
    setCommand(document.getElementById("refresh"), refreshMenubarCommand);
</script>
</body>
</html>

2. 命令模式實現撤銷功能

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
    #ball {
        position: absolute;
        top: 100px;
        left: 0;
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background: #eee;
    }
</style>
</head>
<body>
<input type="text" id="pos">
<button id="excute">excute</button>
<button id="uodo">uodo</button>
<select id="direction">
    <option value="top">top</option>
    <option value="bottom">bottom</option>
    <option value="left" selected>left</option>
    <option value="right">right</option>
</select>
<div id="ball"></div>
<script type="text/javascript">
    var ball = document.getElementById("ball");
    var direction = document.getElementById("direction");
    var pos = document.getElementById("pos");
    var excuteBtn = document.getElementById("excute");
    var uodoBtn = document.getElementById("uodo");
    var moveCommandStack = [];
    var ballAction = {
        dom: ball,
        move: function (direction, pos) {
            this.dom.style[direction] = pos + "px";
        }
    };

    function MoveCommand(recevier, attr, pos) {
        this.recevier = recevier;
        this.pos = pos;
        this.attr = attr;
        this.oldPos = this.recevier.dom.getBoundingClientRect()[attr];
    }

    MoveCommand.prototype.excute = function () {
        this.recevier.move(this.attr, this.pos);
    };
    MoveCommand.prototype.uodo = function () {
        this.recevier.move(this.attr, this.oldPos);
    };

    excuteBtn.onclick = function () {
        var value = pos.value;
        var directionStr = direction.selectedOptions[0].value;
        var command;

        if (!value || !direction) {
            return false;
        }

        command = new MoveCommand(ballAction, directionStr, pos.value);
        command.excute();
        moveCommandStack.push(command);
    };
    uodoBtn.onclick = function () {
        var command = moveCommandStack.pop();

        if (!command) {
            return false;
        }

        command.uodo();
    };
</script>
</body>
</html>

9、中介者模式(解除對象與對象之間的緊耦合關係,全部的相關對象都經過中介者對象來通訊,將多多的的網狀關係解耦成一對多關係)

一、利用發佈-訂閱模式實現中介者模式,將中介者實現爲訂閱者,通訊對象實現爲發佈者,當通訊對象狀態發生改變,便推送消息給中介者,中介者處理消息並通知其餘通訊對象。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        var moduleA = (function () {
            return {
                sendMsg: function (data) {
                    Event.trigger("msgAToB", data);
                },
                receiveMsg: function (data) {
                    console.log(data);
                }
            }
        })();

        var moduleB = (function () {
            return {
                sendMsg: function (data) {
                    Event.trigger("msgBToA", data);
                },
                receiveMsg: function (data) {
                    console.log(data);
                }
            }
        })();

        var Event = {
            listen: function (key, callback) {
                if (!this.clientList[key]) {
                    this.clientList[key] = [];
                }

                this.clientList[key].push(callback);
            },
            trigger: function () {
                var key = Array.prototype.shift.call(arguments),
                    fns = this.clientList[key];

                if (!fns || fns.listen === 0) {
                    return false;
                }

                for (var i = 0, fn; fn = fns[i++];) {
                    fn.apply(this, arguments);
                }
            },
            clientList: {}
        };

        Event.listen("msgAToB", function (data) {
            moduleB.receiveMsg(data);
        });

        Event.listen("msgBToA", function (data) {
            moduleA.receiveMsg(data);
        });

        moduleA.sendMsg("A TO B");
        moduleB.sendMsg("B TO A");
    </script>
</body>
</html>

二、利用普通對象實現中介者模式,只須要中介者開放一些接受通訊者消息的接口。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        var moduleA = (function () {
            return {
                sendMsg: function (receiver, data) {
                    mediator.sendMsg(receiver, data);
                },
                receiveMsg: function (data) {
                    console.log(data);
                }
            }
        })();

        var moduleB = (function () {
            return {
                sendMsg: function (receiver, data) {
                    mediator.sendMsg(receiver, data);
                },
                receiveMsg: function (data) {
                    console.log(data);
                }
            }
        })();

        var mediator = {
            sendMsg: function (receiver, data) {
                receiver.receiveMsg(data);
            }
        };

        moduleA.sendMsg(moduleB, "A TO B");
        moduleB.sendMsg(moduleA, "B TO A");
    </script>
</body>
</html>

10、適配器模式(解決兩個軟件實體間的接口不兼容的問題,使用適配器模式以後,本來因爲接口不兼容而不能工做的兩個軟件實體能夠一塊兒工做,適配器模式更多的是一種補救措施)

一、實例1

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        var googleMap = {
            show: function() {
                console.log("開始渲染谷歌地圖");
            }
        };

        var baiduMap = {
            show: function() {
                console.log("開始渲染百度地圖");
            }
        };

        var gaodeMap = {
            display: function() {
                console.log("開始渲染高德地圖");
            }
        };

        //高德地圖爲後加入的地圖,因爲渲染地圖的接口名稱於其餘地圖不一致,所以須要增長適配器進行轉換
        var gaodeMapAdapter = {
            show: function () {
                return gaodeMap.display();
            }
        };

        var renderMap = function(map) {
            if (map.show instanceof Function) {
                map.show();
            }
        };

        renderMap(googleMap);
        renderMap(baiduMap);
        renderMap(gaodeMapAdapter);
    </script>
</body>
</html>

11、裝飾者模式(給對象動態的增長職責)

一、簡單例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        var plan = {
            fire: function() {
                console.log("發送普通子彈");
            }
        };

        var missileDecorator = function() {
            console.log("發送導彈");
        };

        var atomDecorator = function() {
            console.log("發送原子彈");
        };

        function decoratorPlan(origin, decorator) {
            var originFire = origin.fire;

            origin.fire = function () {
                originFire();
                decorator();
            };
        }

        decoratorPlan(plan, missileDecorator);
        decoratorPlan(plan, atomDecorator);
        plan.fire();
    </script>
</body>
</html>

二、用裝飾者模式擴展window.onload事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function addEventLoad(callback) {
            var oldCallback = window.onload;

            window.onload = function () {
                if (oldCallback) {
                    oldCallback();
                }

                callback();
            };
        }

        addEventLoad(function () {
            console.log(1);
        });

        addEventLoad(function () {
            console.log(2);
        });
    </script>
</body>
</html>

三、利用AOP實現裝飾者模式

AOP:面向切片編程(主要做用是把一些跟核心業務邏輯無關的功能代碼抽離出來)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        Function.prototype.before = function(beforeFn) {
            var self = this;

            return function() {
                beforeFn.apply(this, arguments);
                return self.apply(this, arguments);
            };
        };

        Function.prototype.after = function(afterFn) {
            var self = this;

            return function() {
                var returnValue = self.apply(this, arguments);
                afterFn.apply(this, arguments);
                return returnValue;
            };
        };

        function addEventLoad(fn) {
            window.onload = (window.onload || function() {}).after(fn);
        };

        addEventLoad(function () {
            console.log(1);
        });

        addEventLoad(function () {
            console.log(2);
        });
    </script>
</body>
</html>

12、組合模式(用小的子對象來構建更大的對象)

一、宏命令與組合模式

宏命令:一組命令的集合,經過執行宏命令的方式能夠一次執行一批命令
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        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("打開空調");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var openTvCommand = {
            excute: function() {
                console.log("打開電視");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var openSoundCommand = {
            excute: function() {
                console.log("打開音響");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var macroCommand1 = MacroCommand();
        macroCommand1.add(openTvCommand);
        macroCommand1.add(openSoundCommand);

        var closeDoorCommand = {
            excute: function() {
                console.log("關門");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var openPcCommand = {
            excute: function() {
                console.log("打開電腦");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var openQQCommand = {
            excute: function() {
                console.log("登陸QQ");
            },
            add: function() {
                throw new Error("葉子對象不能添加子節點");
            }
        };

        var macroCommand2 = MacroCommand();
        macroCommand1.add(closeDoorCommand);
        macroCommand1.add(openPcCommand);
        macroCommand1.add(openQQCommand);

        var macroCommand = MacroCommand();
        macroCommand.add(openAcCommand);
        macroCommand.add(macroCommand1);
        macroCommand.add(macroCommand2);

        macroCommand.excute();
    </script>
</body>
</html>

十3、模板方法模式(是一種只需使用繼承就能夠實現的很是簡單的模式,由兩部分構成,抽象父類和具體的實現子類,父類封裝了子類的算法框架和執行順序,是爲了解決相同的行爲在各個子類中重複實現的問題)

一、利用原型繼承咖啡與茶的例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function Beverage() {}

        Beverage.prototype.boilWater = function() {
            console.log("把水煮沸");
        };

        Beverage.prototype.brew = function() {} //空方法,應該由子類重寫
        Beverage.prototype.pourInCup = function() {} //空方法,應該由子類重寫
        Beverage.prototype.addCondiments = function() {} //空方法,應該由子類重寫
        Beverage.prototype.init = function() { //模板方法,該方法封裝了子類的算法框架,它做爲一個算法的模板,指導子類以和政順序去執行哪些方法
            this.boilWater();
            this.brew();
            this.pourInCup();
            if  (this.customerWantsCondiments()) {
                this.addCondiments();
            }
        };

        function Coffee() {}

        Coffee.prototype = new Beverage();
        Coffee.prototype.brew = function() {
            console.log("用沸水沖泡咖啡");
        };
        Coffee.prototype.pourInCup = function() {
            console.log("把咖啡倒進杯子");
        };
        Coffee.prototype.addCondiments = function() {
            console.log("加糖和牛奶");
        };

        function Tea() {}

        Tea.prototype = new Beverage();
        Tea.prototype.brew = function() {
            console.log("用沸水浸泡茶葉");
        };
        Tea.prototype.pourInCup = function() {
            console.log("把茶葉倒進杯子");
        };
        Tea.prototype.addCondiments = function() {
            console.log("加檸檬");
        };
        Tea.prototype.customerWantsCondiments = function(){ //鉤子方法(hook)
            return true;
        };

        var coffee = new Coffee();
        var tea = new Tea();

        coffee.init();
        tea.init();
    </script>
</body>
</html>

二、利用高階函數咖啡與茶的例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function Beverage(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();
        var tea = new Tea();

        coffee.init();
        tea.init();
    </script>
</body>
</html>

十4、享元模式(是一種用於性能優化的模式,享元模式的核心是運用共享技術來有效支持大量細粒度的對象,若是系統中由於建立了大量相似的對象致使內存佔用太高,享元模式就很是有用了,享元模式要求將對象的屬性劃分爲內部狀態和外部狀態,內部狀態通常是不會改變的能夠共享的狀態,外部狀態是改變的狀態)

一、文件上傳例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function Upload(uploadType) {
            this.uploadType = uploadType;
        }

        Upload.prototype.delFile = function(id) {
            uploadManager.setExternaLState(id, this);

            if (this.fileSize < 3000 || window.confirm("肯定要刪除改文件嗎?" + this.fileName)) {
                return this.dom.parentNode.removeChild(this.dom);
            }
        };

        var UploadFactory = (function() {
            var cache = {};

            return {
                create: function(uploadType) {
                    return cache[uploadType] || (cache[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];
                    }
                }
            };
        })();

        var id = 0;

        window.startUpload = function(uploadType, files) {
            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.txt', fileSize: 2000}
        ]);


        startUpload("flash", [
            {fileName: '3.txt', fileSize: 3000},
            {fileName: '4.txt', fileSize: 4000}
        ]);
    </script>
</body>
</html>

十5、職責鏈模式(使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止)

一、例子一

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function order500(orderType, pay, stock) {
            if (orderType === 1 && pay) {
                console.log("500元定金預購,獲得100元優惠券");
            } else {
                return "nextSuccessor"; //我不知道下一個節點是誰,反正把請求日後面傳遞
            }
        }

        function order200(orderType, pay, stock) {
            if (orderType === 2 && pay) {
                console.log("200元定金預購,獲得50元優惠券");
            } else {
                return "nextSuccessor";
            }
        }

        function orderNormal(orderType, pay, stock) {
            if (stock > 0) {
                console.log("普通購買,無優惠");
            } else {
                console.log("手機庫存不足");
            }
        }

        function Chain(fn) {
            this.fn = fn;
            this.successor = null;
        }

        Chain.prototype.setNextSuccessor = function(successor) {
            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);
        chainOrder500.passRequest(2, true, 500);
        chainOrder500.passRequest(3, true, 500);
        chainOrder500.passRequest(1, false, 0);
    </script>
</body>
</html>

二、用AOP實現職責鏈(缺點:疊加了函數的做用域,多層閉包嵌套,影響性能)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function order500(orderType, pay, stock) {
            if (orderType === 1 && pay) {
                console.log("500元定金預購,獲得100元優惠券");
            } else {
                return "nextSuccessor"; //我不知道下一個節點是誰,反正把請求日後面傳遞
            }
        }

        function order200(orderType, pay, stock) {
            if (orderType === 2 && pay) {
                console.log("200元定金預購,獲得50元優惠券");
            } else {
                return "nextSuccessor";
            }
        }

        function 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);
        order(2, true, 500);
        order(1, false, 500);
    </script>
</body>
</html>

十6、狀態模式(狀態模式的關鍵是區分事物內部的狀態,事物內部狀態的改變每每會帶來事物的行爲改變,把事物的每種狀態都封裝成單獨的類,並將請求委託給當前的狀態對象,跟此種狀態有關的行爲都被封裝在這個類內部)

一、簡單例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function OfflightState(light) {
            this.light = light;
        }

        OfflightState.prototype.buttonWasPressed = function() {
            console.log("弱光");
            this.light.setState(this.light.weakLightState);
        };

        function WeaklightState(light) {
            this.light = light;
        }

        WeaklightState.prototype.buttonWasPressed = function() {
            console.log("強光");
            this.light.setState(this.light.strongLightState);
        };

        function StronglightState(light) {
            this.light = light;
        }

        StronglightState.prototype.buttonWasPressed = function() {
            console.log("關燈");
            this.light.setState(this.light.offLightState);
        };

        function Light() {
            this.offLightState = new OfflightState(this);
            this.weakLightState = new WeaklightState(this);
            this.strongLightState = new StronglightState(this);
            this.button = null;
        }

        Light.prototype.setState = function(newState) {
            this.curState = newState;
        };

        Light.prototype.init = function() {
            var button = document.createElement("button"),
                self = this;

            button.innerHTML = "開關";
            this.button = document.body.appendChild(button);

            this.curState = this.offLightState;

            this.button.onclick = function() {
                self.curState.buttonWasPressed();
            };
        };

        var light = new Light();
        light.init();
    </script>
</body>
</html>

二、文件上傳例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        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;
        })();

        function Upload(fileName) {
            this.plugin = plugin;
            this.fileName = fileName;
            this.button1 = null;
            this.button2 = null;
            this.signState = new SignState(this);
            this.uploadingState = new UploadingState(this);
            this.pauseState = new PauseState(this);
            this.doneState = new DoneState(this);
            this.errorState = new ErrorState(this);
            this.currState = this.signState;
        }

        Upload.prototype.init = function() {
            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() {
                self.currState.clickHandler1();
            };

            this.button2.onclick = function() {
                self.currState.clickHandler2();
            };
        };

        Upload.prototype.sign = function() {
            this.plugin.sign();
            this.currState = this.signState;
        };

        Upload.prototype.uploading = function() {
            this.button1.innerHTML = "正在上傳,點擊暫停";
            this.plugin.uploading();
            this.currState = this.uploadingState;
        };

        Upload.prototype.pause = function() {
            this.button1.innerHTML = "已暫停,點擊繼續上傳";
            this.plugin.pause();
            this.currState = this.pauseState;
        };

        Upload.prototype.done = function() {
            this.button1.innerHTML = "上傳完成";
            this.plugin.done();
            this.currState = this.doneState;
        };

        Upload.prototype.error = function() {
            this.button1.innerHTML = "上傳失敗";
            this.currState = this.errorState;
        };

        Upload.prototype.del = function() {
            this.plugin.del();
            this.dom.parentNode.removeChild(this.dom);
        };

        var StateFactory = (function() {
            function State() {}

            State.prototype.clickHandler1 = function() {
                throw Error("子類必須重寫父類的clickHandler1方法");
            };

            State.prototype.clickHandler2 = function() {
                throw Error("子類必須重寫父類的clickHandler2方法");
            };

            return function(param) {
                function F(uploadObj) {
                    this.uploadObj = uploadObj;
                }

                F.prototype = new State();

                for (var i in param) {
                    F.prototype[i] = param[i];
                }

                return F;
            };
        })();

        var SignState = StateFactory({
            clickHandler1: function() {
                console.log("掃描中,點擊無效...");
            },
            clickHandler2: function() {
                console.log("文件正在上傳中,不能刪除");
            }
        });

        var UploadingState = StateFactory({
            clickHandler1: function() {
                this.uploadObj.pause();
            },
            clickHandler2: function() {
                console.log("文件正在上傳中,不能刪除");
            }
        });

        var PauseState = StateFactory({
            clickHandler1: function() {
                this.uploadObj.uploading();
            },
            clickHandler2: function() {
                this.uploadObj.del();
            }
        });

        var DoneState = StateFactory({
            clickHandler1: function() {
                console.log("文件已完成上傳,點擊無效");
            },
            clickHandler2: function() {
                this.uploadObj.del();
            }
        });

        var ErrorState = StateFactory({
            clickHandler1: function() {
                console.log("文件上傳失敗,點擊無效");
            },
            clickHandler2: function() {
                this.uploadObj.del();
            }
        });

        var uploadObj = new Upload("abc.txt");
        uploadObj.init();

        window.external.upload = function(state) {
            uploadObj[state]();
        };

        window.external.upload("sign");

        setTimeout(function() {
            window.external.upload("uploading");
        }, 1000);

        setTimeout(function() {
            window.external.upload("done");
        }, 10000);
    </script>
</body>
</html>

三、js版本的狀態機

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function Light() {
            this.currState = FSM.off;
            this.button = null;
        }

        Light.prototype.init = function() {
            var button = document.createElement("button"),
                self = this;

            button.innerHTML = "已關燈";
            this.button = document.body.appendChild(button);

            this.curState = this.offLightState;

            this.button.onclick = function() {
                self.currState.buttonWasPressed.call(self);
            };
        };

        var FSM = {
            off: {
                buttonWasPressed: function() {
                    console.log("關燈");
                    this.button.innerHTML = "下一次按我就是開燈";
                    this.currState = FSM.on;
                }
            },
            on: {
                buttonWasPressed: function() {
                    console.log("開燈");
                    this.button.innerHTML = "下一次按我就是關燈";
                    this.currState = FSM.off;
                }
            }
        };

        var light = new Light();
        light.init();
    </script>
</body>
</html>
相關文章
相關標籤/搜索