本小節(jié)主要講解三種常用的設(shè)計模式和命名空間,第一種是工廠模式,第二種是單利模式,第三種是觀察者模式
設(shè)計模式概述
是為了解決在開發(fā)中可能遇到的需求(相似),而提出的一套解決方法.
設(shè)計模式要求:
- 在開發(fā)中整個系統(tǒng)需要一套設(shè)計模式(架構(gòu)師)
- 來源:建筑(建房子)領(lǐng)域
- 設(shè)計模式的四人幫: Erich Gamma翩瓜、Richard Helm审姓、Ralph Johnson 和 John Vlissides
- 設(shè)計模式:總共有23種.
- **設(shè)計模式類型:單利(例)模式≈嗨浮|觀察者模式⊥寄亍| 代理模式【翰|工廠模式≌⒆颉|適配器模式〗戎|橋接模式 | .... **
- 設(shè)計模式的書:<設(shè)計模式><大話設(shè)計模式|大話數(shù)據(jù)結(jié)構(gòu)><23種常見的設(shè)計模式>
工廠模式
批量創(chuàng)建大量的同類型的對象
優(yōu)點(diǎn) :
- 可以通過統(tǒng)一的借口來創(chuàng)建對象,根據(jù)傳入的參數(shù)不同來創(chuàng)建不同的對象,易于擴(kuò)展和維護(hù),穩(wěn)定性更好
核心過程 :
- 提供一個父構(gòu)造函數(shù)
- 設(shè)置這個父構(gòu)造函數(shù)的原型對象(屬性|方法)
- 在父構(gòu)造函數(shù)身上添加靜態(tài)工廠方法
1.需要接收傳入的參數(shù)(要生產(chǎn)的產(chǎn)品的類型)
2.判斷 是否支持生產(chǎn)
3.設(shè)置子構(gòu)造函數(shù)的原型對象
4.把新創(chuàng)建的對象返回
定制合作伙伴
直接使用父構(gòu)造函數(shù)的靜態(tài)工廠方法來創(chuàng)建指定的產(chǎn)品對象
示例代碼 :
<script>
//1. 提供一個父構(gòu)造函數(shù)
function PhoneMake(){};
//2. 設(shè)置這個父構(gòu)造函數(shù)的原型對象(屬性|方法)
PhoneMake.prototype.logDes = function(){
console.log("我們的口號是:" + this.des);
}
//3. 在父構(gòu)造函數(shù)身上添加靜態(tài)工廠方法
PhoneMake.factory = function(typeStr){
// 3.1 需要接收傳入的參數(shù)(要生產(chǎn)的產(chǎn)品的類型)
var productType = typeStr;
//var Dog = PhoneMake[productType];
//3.2 判斷 是否支持生產(chǎn)
if (typeof PhoneMake[productType] != "function")
{
//拋出一個異常
throw "對不起,我們工廠和這個品牌沒有商務(wù)合作,不能生產(chǎn)!"
}
//3.3 設(shè)置子構(gòu)造函數(shù)的原型對象
//為了獲得構(gòu)造函數(shù)原型對象的方法
PhoneMake[productType].prototype = new PhoneMake();
//3.4 設(shè)置子構(gòu)造函數(shù)的原型對象
var newProduct = new PhoneMake[productType]();
//3.5 把新創(chuàng)建的對象返回
return newProduct;
}
//4. 定制合作伙伴
PhoneMake.iphone = function(){
this.des = "最安全最穩(wěn)定的系統(tǒng),最垃圾的體驗(yàn)"
}
PhoneMake.oppo = function(){
this.des = "充電兩小時,通話五分鐘"
}
PhoneMake.vivo = function(){
this.des = "照亮你的美,你本來就很美"
}
PhoneMake.meizu = function(){
this.des = "我就是我,不一樣的魅族"
}
//5. 直接使用父構(gòu)造函數(shù)的靜態(tài)工廠方法來創(chuàng)建指定的產(chǎn)品對象
var iphone = PhoneMake.factory("iphone");
var vivo = PhoneMake.factory("vivo");
var oppo = PhoneMake.factory("oppo");
var meizu = PhoneMake.factory("meizu");
var xiaomi = PhoneMake.factory("xiaomi");
iphone.logDes();
vivo.logDes();
oppo.logDes();
meizu.logDes();
</script>
單利模式
在整個程序的運(yùn)行過程中,一個類只有一個實(shí)例對象
js中的單利模式
- js沒有類(ES6才有的) ,js實(shí)現(xiàn)單利模式(限定討論的范圍)
1.字面量
2.內(nèi)置構(gòu)造函數(shù)(Array Date Function Object)
3.工廠函數(shù)
4.自定義構(gòu)造函數(shù)(單利模式)
- js是什么樣的語言
1.弱類型,腳本,輕量級,面向?qū)ο?基于原型(對象),解釋行語言.函數(shù)式.
2.js到底是不是一門面向?qū)ο?類)的語言?
3.js是一門支持面向?qū)ο蟮恼Z言.(封裝|繼承|多態(tài))
單利模式實(shí)現(xiàn)之后的表現(xiàn)
var p1 = new 構(gòu)造函數(shù)()
var p2 = new 構(gòu)造函數(shù)()
p1 == p2
- 示例代碼 :
<script>
function Person(){
//首先創(chuàng)建一個空的對象
//默認(rèn)把新的對象賦值給this
//把新對象返回
}
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);
</script>
單利模式的實(shí)現(xiàn)方式01 ----> 全局變量
全局變量來保存對象實(shí)現(xiàn)單利模式
- 提供一個全局的變量
- 提供一個構(gòu)造函數(shù)Person
- 在構(gòu)造函數(shù)內(nèi)部先判斷全局變量是否有值,如果有那么就直接返回
- 如果沒有,那么就把this賦值給全局變量
- 通過this設(shè)置屬性和方法
存在的問題
使用一個全局變量來保存單利對象,該全局變量在整個作用域中都可以被訪問或者是修改,可能會輕易的被覆蓋或者是修改.
修改之后,創(chuàng)建出來的實(shí)例對象就不再是之前的那個單利對象了.
示例代碼 :
<script>
var instance;
function Person(){
if(instance)
{
console.log("對象已經(jīng)被創(chuàng)建,直接把之前創(chuàng)建好的對象返回");
return instance;
}
instance = this;
this.name = "奧特曼";
this.age = 1000;
console.log("第一次創(chuàng)建對象,創(chuàng)建對象之后并返回");
}
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);
var p3 = new Person();
instance = "demo";
var p4 = new Person();
console.log(p4);
console.log(p4 == p1);
</script>
單利模式的實(shí)現(xiàn)方式02 ----> 靜態(tài)屬性
靜態(tài)成員:直接添加到構(gòu)造函數(shù)身上的屬性或者是方法
存在的問題
構(gòu)造函數(shù)的靜態(tài)屬性其實(shí)也可能被修改,因此這種方法也不安全
示例代碼 :
<script>
function Person(){
//判斷對象是否已經(jīng)被創(chuàng)建
if (Person.instance)
{
console.log("之前已經(jīng)創(chuàng)建過對象,直接返回");
return Person.instance;
}
this.name = "大黃蜂"
Person.instance = this;
console.log("第一次創(chuàng)建");
}
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);
instance = "demo";
var p3 = new Person();
console.log(p1 == p3);
Person.instance = "123";
var p4 = new Person();
console.log(p4 == p1);
</script>
單利模式的實(shí)現(xiàn)方式03 ----> 惰性函數(shù)
核心過程
提供一個構(gòu)造函數(shù)
在構(gòu)造函數(shù)內(nèi)部聲明一個私有的變量
使用惰性函數(shù)定義更新構(gòu)造函數(shù)的實(shí)現(xiàn)(直接把instance返回)
設(shè)置原型對象[新構(gòu)造函數(shù)的原型對象 = 舊構(gòu)造函數(shù)的原型對象]
構(gòu)造函數(shù),prototype == 對象.proto使用新的構(gòu)造函數(shù)創(chuàng)建實(shí)例對象,并且賦值給instance
修正對象的構(gòu)造函數(shù)指向
通過instance設(shè)置實(shí)例屬性和方法
示例代碼 :
<script>
// 01 提供一個構(gòu)造函數(shù)
function Person(){
//this01
//02 在構(gòu)造函數(shù)內(nèi)部聲明一個私有的變量
var instance;
//03 使用惰性函數(shù)定義更新構(gòu)造函數(shù)的實(shí)現(xiàn)(直接把instance返回)
Person = function(){
//內(nèi)部默認(rèn)會創(chuàng)建一個空的對象 this02
return instance;
}
//04 設(shè)置原型對象[新構(gòu)造函數(shù)的原型對象 = 舊構(gòu)造函數(shù)的原型對象]
//原型鏈繼承:Man.prototype = new Person();
//原型式繼承:Man.prototype = Person.prototype;
//Person.prototype = this.__proto__; //非標(biāo)準(zhǔn)(代碼中不要出現(xiàn))
Person.prototype = this;
//05 使用新的構(gòu)造函數(shù)創(chuàng)建實(shí)例對象,并且賦值給instance
instance = new Person();
//instance = this;
//06 修正對象的構(gòu)造函數(shù)指向
instance.constructor = Person;
// 07 通過instance設(shè)置實(shí)例屬性和方法
instance.name = "我很好聽";
// 08 把instance返回
return instance;
}
Person.prototype.des = "描述信息";
var p1 = new Person();
Person.prototype.hi = "hi";
var p2 = new Person();
console.log(p1 == p2);
console.log(p1.constructor == Person); //true
console.log(p1.des);
console.log(p1.hi);
</script>
單利模式的實(shí)現(xiàn)方式04 ----> 全局變量 + 即時函數(shù)
- 示例代碼 :
<script>
var Person;
(function(){
var instance;
Person = function (){
if(instance)
{
return instance;
}
this.name = "momo";
instance = this;
}
})();
var p1 = new Person();
var p2 = new Person();
console.log(p1 == p2);
</script>
觀察者模式
觀察者模式舉例說明
男生A和男生B同時都喜歡女生C,他們想時時監(jiān)視女生C的動向,動態(tài),所以他們找了女生C的閨蜜作為觀察者幫他們監(jiān)視實(shí)時動態(tài).這樣不管女生C有什么最新的動態(tài)都會被男生A和男生B監(jiān)聽到,開發(fā)中就是男生多了點(diǎn),動態(tài)多了點(diǎn),核心內(nèi)容就是這樣
要求:
女神:rose(發(fā)布者)
男生:jack(訂閱者)
男生:tom(訂閱者)
- 過程:
創(chuàng)建或者是設(shè)置一個發(fā)布者
創(chuàng)建訂閱者對象
注冊訂閱者
測試(發(fā)狀態(tài))
- 示例代碼1 : 一個發(fā)布者,兩個訂閱者,關(guān)注的是一個狀態(tài)
<script>
//01 創(chuàng)建或者是設(shè)置一個發(fā)布者
var rose = {
user:[],
addUser:function(fn){
if (typeof fn != "function")
{
throw "不支持該操作!";
}
this.user.push(fn);
},
removeUser:function(fn){
for (var i = 0; i < this.user.length; i++) {
if(this.user[i] == fn)
{
console.log(fn + "取消了訂閱");
this.user.splice(i,1);
}
}
},
eat:function(){
for (var i = 0; i < this.user.length; i++) {
this.user[i]();
}
}
}
// 02 創(chuàng)建訂閱者對象
var jack = {
eat_jack:function(){
console.log("我陪你去吃拉面吧 ---jack");
}
}
var tom = {
eat_tom:function(){
console.log("我陪你去吃壽司吧 ---tom");
}
}
// 03 注冊訂閱者
rose.addUser(jack.eat_jack);
rose.addUser(tom.eat_tom);
//04 發(fā)布者狀態(tài)改變
rose.eat();
rose.removeUser(jack.eat_jack);
rose.eat();
</script>
- 示例代碼2 : 多個狀態(tài)
<script>
//01 創(chuàng)建或者是設(shè)置一個發(fā)布者
var publisher = {
addUser:function(fn,type){
var type = type || "eat";
if (typeof fn != "function")
{
throw "不支持該操作!";
}
this.user[type].push(fn);
},
removeUser:function(fn,type){
var type = type || "eat";
for (var i = 0; i < this.user[type].length; i++) {
if(this.user[type][i] == fn)
{
console.log(fn + "取消了訂閱");
this.user[type].splice(i,1);
}
}
},
eat:function(){
for (var i = 0; i < this.user["eat"].length; i++) {
this.user["eat"][i]();
}
},
sleep:function(){
for (var i = 0; i < this.user["sleep"].length; i++) {
//console.log(this.user,"++++");
this.user["sleep"][i]();
}
}
}
var rose = {};
//封裝一個函數(shù)用來快速的讓某個指定對象成為發(fā)布者
function makePublisher(o){
for(var i in publisher)
{
if (publisher.hasOwnProperty(i) && typeof publisher[i] == "function" ){
o[i] = publisher[i];
}
}
o.user = {
eat:[],
sleep:[]
};
}
makePublisher(rose);
// 02 創(chuàng)建訂閱者對象
var jack = {
eat_jack:function(){
console.log("我陪你去吃拉面吧 ---jack");
},
sleep_jack:function(){
console.log("晚安 rose ---jack");
}
}
// 03 注冊訂閱者
rose.addUser(jack.eat_jack,"eat"); //關(guān)注rose肚子餓不餓
rose.addUser(jack.sleep_jack,"sleep"); //關(guān)注rose困不困
rose.eat();
rose.sleep();
- 示例代碼3 : 通用性處理
<script>
//01 創(chuàng)建或者是設(shè)置一個發(fā)布者
var publisher = {
addUser:function(fn,type){
var type = type || "eat";
if (this.user[type] == undefined)
{
this.user[type] = [];
}
if (typeof fn != "function")
{
throw "不支持該操作!";
}
this.user[type].push(fn);
},
removeUser:function(fn,type){
this.publish(type,fn);
},
publish:function(type,fn){
var type = type || "eat";
for (var i = 0; i < this.user[type].length; i++) {
//判斷當(dāng)前是要取消訂閱還是要發(fā)布狀態(tài)
if (typeof fn == "function")
{
if(this.user[type][i] == fn)
{
console.log(fn + "取消了訂閱");
this.user[type].splice(i,1);
}
}else
{
this.user[type][i]();
}
}
}
}
var rose = {
eat:function(){
this.publish("eat");
},
sleep:function(){
this.publish("sleep");
},
read:function(){
this.publish("read");
}
};
function makePublisher(o){
for(var i in publisher)
{
if (publisher.hasOwnProperty(i) && typeof publisher[i] == "function" ){
o[i] = publisher[i];
}
}
o.user = {
eat:[],
sleep:[]
};
}
makePublisher(rose);
var jack = {
eat_jack:function(){
console.log("我陪你去吃拉面吧 ---jack");
},
sleep_jack:function(){
console.log("晚安 rose ---jack");
}
}
var tom = {
eat_tom:function(){
console.log("我買給你吧 ---tom");
},
sleep_tom:function(){
console.log("今晚的太陽很好看 ---tom");
},
read_tom:function(){
console.log("你也在學(xué)習(xí)js嗎?");
}
}
rose.addUser(jack.eat_jack,"eat");
rose.addUser(tom.sleep_tom,"sleep");
rose.addUser(tom.read_tom,"read");
rose.eat();
rose.sleep();
rose.read();
</script>
- 示例代碼4 : 訂閱者成為發(fā)布者
<script>
//01 創(chuàng)建或者是設(shè)置一個發(fā)布者
var publisher = {
addUser:function(fn,type){
var type = type || "eat";
if (this.user[type] == undefined)
{
this.user[type] = [];
}
if (typeof fn != "function")
{
throw "不支持該操作!";
}
this.user[type].push(fn);
},
removeUser:function(fn,type){
this.publish(type,fn);
},
publish:function(type,fn){
var type = type || "eat";
for (var i = 0; i < this.user[type].length; i++) {
//判斷當(dāng)前是要取消訂閱還是要發(fā)布狀態(tài)
if (typeof fn == "function")
{
if(this.user[type][i] == fn)
{
console.log(fn + "取消了訂閱");
this.user[type].splice(i,1);
}
}else
{
this.user[type][i]();
}
}
}
}
var rose = {
eat:function(){
this.publish("eat");
},
sleep:function(){
this.publish("sleep");
},
read:function(){
this.publish("read");
},
lol_rose:function(){
console.log("你怎么又在打游戲?還是游戲比較重要一些?")
}
};
function makePublisher(o){
for(var i in publisher)
{
if (publisher.hasOwnProperty(i) && typeof publisher[i] == "function" ){
o[i] = publisher[i];
}
}
o.user = {
eat:[],
sleep:[]
};
}
makePublisher(rose);
var jack = {
eat_jack:function(){
console.log("我陪你去吃拉面吧 ---jack");
},
sleep_jack:function(){
console.log("晚安 rose ---jack");
},
statusLol:function(){
this.publish("lol");
}
}
var tom = {
eat_tom:function(){
console.log("我買給你吧 ---tom");
},
sleep_tom:function(){
console.log("今晚的太陽很好看 ---tom");
},
read_tom:function(){
console.log("你也在學(xué)習(xí)js嗎?");
},
lol_rose:function(){
console.log("好兄弟,終于來啦");
}
}
rose.addUser(jack.eat_jack,"eat");
rose.addUser(tom.sleep_tom,"sleep");
rose.addUser(tom.read_tom,"read");
rose.eat();
rose.sleep();
rose.read();
//設(shè)置jack成為發(fā)布者,狀態(tài)(lol)
makePublisher(jack);
jack.addUser(rose.lol_rose,"lol");
jack.addUser(tom.lol_rose,"lol");
jack.statusLol();
</script>
備忘模式(函數(shù)結(jié)構(gòu)緩存)
特定場合:
- 計算的函數(shù)f()
- 某些參數(shù)需要進(jìn)行大規(guī)模反復(fù)的計算,可以考慮吧計算的結(jié)果保存起來
f(n) ..... =>1000m
代碼中某些參數(shù)可能會反復(fù)計算
f(10) ===>ssddd
f(10) ===>ssddd
使用一個緩存對象cacheObj{key-value}
- 思路:
1.提供一個全局的對象(緩存對象),key-value
2.當(dāng)我們傳遞參數(shù)需要進(jìn)行計算(邏輯)的時候,先檢查緩存對象中是否有對應(yīng)的結(jié)果
3.如果有緩存數(shù)據(jù),那么就直接使用(可以節(jié)省時間,提高效率)
4.如果沒有緩存數(shù)據(jù),那么這個時候再執(zhí)行計算操作,處理得到結(jié)果之后,把這個數(shù)據(jù)保存起來
5.函數(shù)的參數(shù)作為緩存對象的key,把函數(shù)計算的結(jié)果作為這個key對應(yīng)的值
- 示例代碼 :
<script>
var cache = {};
function f1(str){
//.....
if (cache[str] != undefined)
{
console.log("已經(jīng)存在緩存數(shù)據(jù),直接返回");
return cache[str];
}
//....如果緩存中有數(shù)據(jù),那么函數(shù)體后面的代碼就不再執(zhí)行(節(jié)省時間)
//執(zhí)行耗時操作...
var result = str + " hello world!";
cache[str] = result;
console.log("第一次調(diào)用函數(shù)傳入?yún)?shù),返回結(jié)果");
return result;
}
console.log(f1("demo")); //
console.log(f1("demo")); //
console.log(f1("demo"));
</script>
<script>
function f1(str){
//.....
if (f1.cache[str] != undefined)
{
console.log("已經(jīng)存在緩存數(shù)據(jù),直接返回");
return f1.cache[str];
}
//....如果緩存中有數(shù)據(jù),那么函數(shù)體后面的代碼就不再執(zhí)行(節(jié)省時間)
//執(zhí)行耗時操作...
var result = str + " hello world!";
f1.cache[str] = result;
console.log("第一次調(diào)用函數(shù)傳入?yún)?shù),返回結(jié)果");
return result;
}
f1.cache = {};
console.log(f1("test")); //
console.log(f1("test")); //
console.log(f1("test"));
</script>
命名空間模式:
寫法:就是把所有的東西都寫在一個對象里面.
命名:命名空間的名稱一般是項目的名稱或者是簡寫,要求所有的字符都大寫
- 示例代碼 :
<script>
//01 普通的變量
var a = "a";
var b = "b";
//02 對象
var obj = {
name:"xxx"
}
function func (){
console.log("func");
}
function Person(){
this.name = "默認(rèn)的名稱";
}
function func (){
console.log("func");
}
</script>
<script>
var MOMO = {};
//01 普通的變量
MOMO.a = "a";
MOMO.b = "b";
//02 對象
MOMO.obj = {
name:"zahngsan"
}
MOMO.func = function(){
console.log("func");
}
MOMO.Person = function(){
this.name = "默認(rèn)的名稱";
}
console.log(new MOMO.Person());
</script>
<script>
// var MOMO ={};
// MOMO.obj = {
// name:"張三"
// };
//
// MOMO.obj = "demodede";
//在使用或者是對屬性進(jìn)行賦值之前,會先做一個檢查(檢查改屬性或者是方法是否存在)
//var MOMO = {}; 不推薦
//02 更安全的處理方式:麻煩
// if (MOMO == undefined)
// {
// var MOMO = {};
// }
var MOMO = MOMO || {}; //邏輯或 如果MOMO為真那么返回MOMO,否則就返回{}
//if (MOMO.name == undefined)
if ( typeof MOMO.name == 'undefined')
{
MOMO.name = "測試的名稱";
}
if ( typeof MOMO.age == 'undefined')
{
MOMO.age = 20;
}
//100屬性
</script>
通用的命名空間函數(shù)
在命名空間上面提供一個方法(nameSpace)
- 得到調(diào)用函數(shù)傳入的字符串
- 把字符串轉(zhuǎn)換為字符串?dāng)?shù)組
- 把MOMO最外層去掉(刪除)
- 遍歷
- 示例代碼 :
<script>
var MOMO = MOMO || {};
MOMO.namespace = function(stringParam){
var str = stringParam;
var parts = str.split("."); //根據(jù)字符串切割字符串變成一個數(shù)組
var parent = MOMO;
console.log(parts);
if(parts[0] == "MOMO")
{
parts = parts.slice(1) //作用:刪除第一個元素返回一個新的數(shù)組
}
for (var i = 0; i < parts.length; i++) {
//MOMO.name
//name.des
//des.abc
if (parent[parts[i]] == undefined)
{
parent[parts[i]] = {};
}
//更新父節(jié)點(diǎn)
parent = parent[parts[i]];
}
}
MOMO.namespace("MOMO.name.des.abc");
console.log(MOMO);
MOMO.namespace("MOMO.a.b.c.d.e.f.d.g.h.j.k.s.d.g.h.j.a.s.d.f.we.r");
console.log(MOMO);
MOMO.namespace("abc.des.abc");
console.log(MOMO);
</script>