前端面試之JavaScript設計模式

引言

面向對象編程就是將你的需求抽象成一個對象,而後對這個對象進行分析,爲其添加對應的特徵(屬性)與行爲(方法),咱們將這個對象稱之爲 。 面向對象一個很重要的特色就是封裝,雖然 javascript 這種解釋性的弱類型語言沒有像一些經典的強類型語言(例如C++,JAVA等)有專門的方式用來實現類的封裝,但咱們能夠利用 javascript 語言靈活的特色,去模擬實現這些功能。而在許多大型web項目中國,JavaScript代碼的數量已經很是多了,咱們有必要將一些優秀的設計模式借鑑到JavaScript中。javascript

裝飾者模式

給對象動態地添加職責的方式稱爲裝飾者模式。傳統的面嚮對象語言中給對象添加功能經常使用繼承的方式,可是繼承的方式不靈活,而與之相比,裝飾者模式更加靈活,「即用即付」。html

AOP 裝飾函數:java

Function.prototype.before = function(fn) {
  const self = this
  return function() {
    fn.apply(new(self), arguments)  // https://github.com/MuYunyun/blog/pull/30#event-1817065820
    return self.apply(new(self), arguments)
  }
}

Function.prototype.after = function(fn) {
  const self = this
  return function() {
    self.apply(new(self), arguments)
    return fn.apply(new(self), arguments)
  }
}
複製代碼

場景:插件式的表單驗證

<html>
<body> 用戶名:<input id="username" type="text"/> 密碼: <input id="password" type="password"/> <input id="submitBtn" type="button" value="提交"></button> </body> <script> var username = document.getElementById( 'username' ), password = document.getElementById( 'password' ), submitBtn = document.getElementById( 'submitBtn' ); var formSubmit = function(){ if ( username.value === '' ){ return alert ( '用戶名不能爲空' ); } if ( password.value === '' ){ return alert ( '密碼不能爲空' ); } var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); // ajax 具體實現略 } submitBtn.onclick = function(){ formSubmit(); } var validata = function(){ if ( username.value === '' ){ alert ( '用戶名不能爲空' ); return false; } if ( password.value === '' ){ alert ( '密碼不能爲空' ); return false; } } </script> </html> 複製代碼

上面formSubmit函數除了提交ajax請求以外,還要驗證用戶輸入的合法,這樣會形成函數臃腫,職責混亂,咱們能夠分離檢驗輸入和ajax請求的代碼,咱們把校驗輸入的邏輯放在validata函數中,而且約定當validata函數返回false的時候,表示校驗未經過git

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();
	}
複製代碼

發佈-訂閱模式

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

場景:售房信息通知

var salesOffices = {}; // 定義售樓處
	salesOffices.clientList = []; // 緩存列表,存放訂閱者的回調函數

	salesOffices.listen = function( key, fn ){
		if ( !this.clientList[ key ] ){ // 若是尚未訂閱過此類消息,給該類消息建立一個緩存列表
			this.clientList[ key ] = [];
		}
		this.clientList[ key ].push( fn ); // 訂閱的消息添加進消息緩存列表
	};

	salesOffices.trigger = function(){ // 發佈消息
		var key = Array.prototype.shift.call( arguments ), // 取出消息類型
		fns = this.clientList[ key ]; // 取出該消息對應的回調函數集合
		if ( !fns || fns.length === 0 ){ // 若是沒有訂閱該消息,則返回
			return false;
		}
		for( var i = 0, fn; fn = fns[ i++ ]; ){
			fn.apply( this, arguments ); // (2) // arguments 是發佈消息時附送的參數
		}
	};

	salesOffices.listen( 'squareMeter88', function( price ){ // 小明訂閱88 平方米房子的消息
		console.log( '價格= ' + price ); // 輸出: 2000000
	});

	salesOffices.listen( 'squareMeter110', function( price ){ // 小紅訂閱110 平方米房子的消息
		console.log( '價格= ' + price ); // 輸出: 3000000
	});

	salesOffices.trigger( 'squareMeter88', 2000000 ); // 發佈88 平方米房子的價格
	salesOffices.trigger( 'squareMeter110', 3000000 ); // 發佈110 平方米房子的價格
複製代碼

狀態模式

狀態模式的關鍵在於區分事物內部的狀態,事物內部狀態的改變每每會帶來事物的行爲改變github

  • 狀態模式定義了狀態與行爲之間的關係,並將它們封裝在一個類中。
  • context中的請求動做和狀態類中封裝的行爲能夠很是容易地獨立變化而互不影響

場景:電燈程序

電燈開關按鈕被按下時,第一次按下打開弱光,第二次按下打開強光,第三次是關閉電燈。咱們談到封裝,通常都會優先封裝對象的行爲,而不是對象的狀態。但在狀態模式中恰好相反,狀態模式的關鍵是把事物的每種狀態都封裝成單獨的類,跟此種狀態有關的行爲都被封裝在這個類的內部,因此button被按下的時候,只須要在上下文中,把這個請求委託給當前的狀態對象便可,該狀態對象會負責渲染它自身的行爲。web

var OffLightState = function( light ){
		this.light = light;
	};

	OffLightState.prototype.buttonWasPressed = function(){
		console.log( '弱光' ); // offLightState 對應的行爲
		this.light.setState( this.light.weakLightState ); // 切換狀態到weakLightState
	};
// WeakLightState:
var WeakLightState = function( light ){
	this.light = light;
};

WeakLightState.prototype.buttonWasPressed = function(){
		console.log( '強光' ); // weakLightState 對應的行爲
		this.light.setState( this.light.strongLightState ); // 切換狀態到strongLightState
	};
	// StrongLightState:
	var StrongLightState = function( light ){
		this.light = light;
	};

	StrongLightState.prototype.buttonWasPressed = function(){
		console.log( '關燈' ); // strongLightState 對應的行爲
		this.light.setState( this.light.offLightState ); // 切換狀態到offLightState
	};

//這個light稱爲上下文(context)
	var Light = function(){
       //在light的構造函數中,咱們要建立每個狀態類的實例對象,context持有這些狀態對象的引用,以便將請求委託給狀態對象。用戶的請求,即點擊button的動做也是實如今Context中的
		this.offLightState = new OffLightState( this );
		this.weakLightState = new WeakLightState( this );
		this.strongLightState = new StrongLightState( this );
		this.button = null;
	};


	Light.prototype.init = function(){
		var button = document.createElement( 'button' ),
		self = this;
		this.button = document.body.appendChild( button );
		this.button.innerHTML = '開關';
		this.currState = this.offLightState; // 設置當前狀態
		this.button.onclick = function(){
			self.currState.buttonWasPressed();
		}	
	};

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

	var light = new Light();
	light.init();
複製代碼

JavaScript狀態機:ajax

var delegate = function( client, delegation ){
		return {
			buttonWasPressed: function(){ // 將客戶的操做委託給delegation 對象
				return delegation.buttonWasPressed.apply( client, arguments );
			}
		}
	};

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

	var Light = function(){
		this.offState = delegate( this, FSM.off );
		this.onState = delegate( this, FSM.on );
		this.currState = this.offState; // 設置初始狀態爲關閉狀態
		this.button = null;
	};

	Light.prototype.init = function(){
		var button = document.createElement( 'button' ),
		self = this;
		button.innerHTML = '已關燈';
		this.button = document.body.appendChild( button );
		this.button.onclick = function(){
			self.currState.buttonWasPressed();
		}
	};
	var light = new Light();
	light.init();
複製代碼

代理模式

代理有不少種類,好比保護代理會過濾請求,代理B能夠拒絕將A發起的請求給傳遞給C;虛擬代理會將一些操做交給代理執行,代理B能夠在A知足某些條件下再執行操做,即虛擬代理會把一些開銷大的對象延遲到真正須要它的時候再去建立。保護代理用於控制不一樣權限的對象對目標對象的訪問,但在JavaScript中並不太容易實現保護代理,由於咱們沒法判斷誰訪問了某個對象。算法

代理本體的接口必須保持一致性,對於用戶,他們並不清楚代理和本體的區別,在任何使用本體的地方均可以替換成使用代理。編程

場景:圖片預加載

//只有本體
var myImage=(function(){
    var imgNode=document.createElement('img');
    document.body.appendChild(imgNode);
    
    return{
        setSrc:function(src){
            imgNode.src=src;
        }
    }
})
myImg.setSrc('./logo.lpg');

//有本體和代理的狀況;引入代理對象proxyImage,經過這個代理對象,在圖片被真正加載好以前,頁面將出現一張佔位的菊花圖來提示用戶圖片正在被加載
var myImage=(function(){
    var imgNode=document.createElement('img');
    document.body.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('./logo.jpg')
複製代碼

策略模式

策略模式的目的就是將算法的使用與算法的實現分離開,一個策略模式的程序至少由兩部分組成。第一個部分是策略類,策略類封裝了具體的算法,並負責具體的計算過程。第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類。

傳統面嚮對象語言實現

/*strategy類*/
var performanceS=function(){};

performanceS.prototype.caculate=function(salary){
    return salary*4;
}

var performanceA=function(){};

performanceA.prototype.caculate=function(salary){
    return salary*3;
}

var performanceB=function(){};

performanceB.proptype.caculate=function(salary){
    return salary*2;
}

/*Context類*/
var Bonus=function(){
    this.salary=null;
    this.strategy=null;
}

Bonus.prototype.setSalary=function(salary){
    this.salary=salary;
}
Bonus.prototype.setStrategy=function(strategy){
    this.strategy=strategy;
}
Bonus.prototype.getBonus=function(){
    return this.strategy.caculate(this.salary);
}

var bonus=new Bonus();
bonus.setSalary(1000);
bonus.setStrategy(new performanceS());
console.log(bonus.getBonus());
複製代碼

Javascript中實現

在JavaScript中,Stratege咱們能夠把它定義爲函數對象,Context負責接收用戶的請求並委託給strategy對象

var strategies={
    "S":function(salary){
        return salary*4;
    },
    "A":function(salary){
        return salary*3;
    },
    "B":function(salary){
        return salary*2;
    }
};

var calculateBonus=function(level,salary){
    return strategies[level](salary);
};

console.log(calculateBonus('S',2000));
複製代碼

場景:表單檢驗

/*Validator類的實現*/
var Validator = function(){
    this.cache = [];
}
Validator.prototype.add =function(dom,rule,errorMsg){
    var ary = rule.split(':');
    this.cache.push(function(){
        var strategy =ary.shift();
        ary.unshift(dom.value);
        ary.push(errorMsg);
        return strategies[strategy].apply(dom,ary);
    });
};

Validator.prototype.start = function(){
    for(var i=0,validatorFunc;validatorFunc = this.cache[i++]){
        var msg=validatorFunc(); 
        if(msg){
            return msg;
        }
    }
}

/*定義strategy*/
var strategies = {
    isNonEmpty:function(value,errorMsg){
        if(value=''){
            return errorMsg;
        }
    },
    minLength:function(value,length,errorMsg){
        if(value.length<length){
            return errorMsg
        }
    },
    isMobile:function(value,errorMsg){
        if(!/^1[3|5|8][0-9]$/.test(value)){
            return errorMsg;
        }
    }
}

/*定義Context*/
var validataFunc = function(){
    var validator = new Validator();
    validator.add(registerForm.userName,'isNonEmpty','用戶名不能爲空’);
    validator.add(registerForm.password,'inLength:6',密碼長度不能少於6位'')
    validator.add(registerForm.phoneNumber,'isMobile','手機號碼格式不正確’);

    var errorMsg = validator.start();
    return errorMsg;
}

var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    varerrorMsg = validataFunc(); 
    if(errorMsg){
        alert(errorMsg);
        return false;
    }
}
複製代碼

職責鏈模式

職責鏈模式: 相似多米諾骨牌, 經過請求第一個條件, 會持續執行後續的條件, 直到返回結果爲止。

img

重要性: 4 星, 在項目中能對 if-else 語句進行優化

場景 :手機售賣

場景: 某電商針對已付過定金的用戶有優惠政策, 在正式購買後, 已經支付過 500 元定金的用戶會收到 100 元的優惠券, 200 元定金的用戶能夠收到 50 元優惠券, 沒有支付過定金的用戶只能正常購買。

// orderType: 表示訂單類型, 1: 500 元定金用戶;2: 200 元定金用戶;3: 普通購買用戶
// pay: 表示用戶是否已經支付定金, true: 已支付;false: 未支付
// stock: 表示當前用於普通購買的手機庫存數量, 已支付過定金的用戶不受此限制

const order = function( orderType, pay, stock ) {
  if ( orderType === 1 ) {
    if ( pay === true ) {
      console.log('500 元定金預購, 獲得 100 元優惠券')
    } else {
      if (stock > 0) {
        console.log('普通購買, 無優惠券')
      } else {
        console.log('庫存不夠, 沒法購買')
      }
    }
  } else if ( orderType === 2 ) {
    if ( pay === true ) {
      console.log('200 元定金預購, 獲得 50 元優惠券')
    } else {
      if (stock > 0) {
        console.log('普通購買, 無優惠券')
      } else {
        console.log('庫存不夠, 沒法購買')
      }
    }
  } else if ( orderType === 3 ) {
    if (stock > 0) {
        console.log('普通購買, 無優惠券')
    } else {
      console.log('庫存不夠, 沒法購買')
    }
  }
}

order( 3, true, 500 ) // 普通購買, 無優惠券
複製代碼

下面用職責鏈模式改造代碼:

const order500 = function(orderType, pay, stock) {
  if ( orderType === 1 && pay === true ) {
    console.log('500 元定金預購, 獲得 100 元優惠券')
  } else {
    order200(orderType, pay, stock)
  }
}

const order200 = function(orderType, pay, stock) {
  if ( orderType === 2 && pay === true ) {
    console.log('200 元定金預購, 獲得 50 元優惠券')
  } else {
    orderCommon(orderType, pay, stock)
  }
}

const orderCommon = function(orderType, pay, stock) {
  if (orderType === 3 && stock > 0) {
    console.log('普通購買, 無優惠券')
  } else {
    console.log('庫存不夠, 沒法購買')
  }
}

order500( 3, true, 500 ) // 普通購買, 無優惠券
複製代碼

改造後能夠發現代碼相對清晰了, 可是鏈路代碼和業務代碼依然耦合在一塊兒, 進一步優化:

// 業務代碼
const order500 = function(orderType, pay, stock) {
  if ( orderType === 1 && pay === true ) {
    console.log('500 元定金預購, 獲得 100 元優惠券')
  } else {
    return 'nextSuccess'
  }
}

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

const orderCommon = function(orderType, pay, stock) {
  if (orderType === 3 && stock > 0) {
    console.log('普通購買, 無優惠券')
  } else {
    console.log('庫存不夠, 沒法購買')
  }
}

// 鏈路代碼
const chain = function(fn) {
  this.fn = fn
  this.sucessor = null
}

chain.prototype.setNext = function(sucessor) {
  this.sucessor = sucessor
}

chain.prototype.init = function() {
  const result = this.fn.apply(this, arguments)
  if (result === 'nextSuccess') {
    this.sucessor.init.apply(this.sucessor, arguments)
  }
}

const order500New = new chain(order500)
const order200New = new chain(order200)
const orderCommonNew = new chain(orderCommon)

order500New.setNext(order200New)
order200New.setNext(orderCommonNew)

order500New.init( 3, true, 500 ) // 普通購買, 無優惠券
複製代碼

重構後, 鏈路代碼和業務代碼完全地分離。假如將來須要新增 order300, 那隻需新增與其相關的函數而沒必要改動原有業務代碼。

另外結合 AOP 還能簡化上述鏈路代碼:

// 業務代碼
const order500 = function(orderType, pay, stock) {
  if ( orderType === 1 && pay === true ) {
    console.log('500 元定金預購, 獲得 100 元優惠券')
  } else {
    return 'nextSuccess'
  }
}

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

const orderCommon = function(orderType, pay, stock) {
  if (orderType === 3 && stock > 0) {
    console.log('普通購買, 無優惠券')
  } else {
    console.log('庫存不夠, 沒法購買')
  }
}

// 鏈路代碼
Function.prototype.after = function(fn) {
  const self = this
  return function() {
    const result = self.apply(self, arguments)
    if (result === 'nextSuccess') {
      return fn.apply(self, arguments) // 這裏 return 別忘記了~
    }
  }
}

const order = order500.after(order200).after(orderCommon)

order( 3, true, 500 ) // 普通購買, 無優惠券
複製代碼

職責鏈模式比較重要, 項目中能用到它的地方會有不少, 用上它能解耦 1 個請求對象和 n 個目標對象的關係。

單例模式

單例模式的定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點

應用:線程池、全局緩存,瀏覽器中的window對象等

傳統面嚮對象語言中實現

var SingleTon =function(name){
    this.name=name;
    this.instance=null;
}

SingleTon.proptype.getName=function(){
    alert(this.name);

}

SingleTon.getInstance=function(name){
    if(!this.instance){
        this.instance=new SingleTon(name);
    }
    return this.instance;
}

var a=SingleTon.getInstance('sven1');
var b=SingleTon.getInstance('sven2');
複製代碼

或者將this.intance改成局部變量的形式

var SingleTon =function(name){
    this.name=name;
}

SingleTon.proptype.getName=function(){
    alert(this.name);

}

SingleTon.getInstance=function(name){
    var instance=null;
    return function(name) {
        if (!instance) {
            this.instance = new SingleTon(name);
        }
        return this.instance;
    }
}

var a=SingleTon.getInstance('sven1');
var b=SingleTon.getInstance('sven2');
複製代碼

更加透明的單例類,用戶建立對象時,能夠像使用其餘任何普通類同樣

//這段代碼既建立了對象,又返回了單例,跟其餘建立對象方式無異
var CreateDiv=(function(){
    var instance;
    var CreateDiv=function(html){
        if(instance){
            return instance;
        }
        this.htm=html;
        this.init();
        return instance=this;
    };


    CreateDiv.proptype.init=function(){
        var div=document.createElement('div');
        div.innerHTML=this.html;
        document.body.appendChild(div);
    };
    return CreateDiv;
})()

var a=new CreateDiv('sven1');
var b=new CreateDiv('sven2');
複製代碼

咱們還須要將建立對象,執行初始化init方法與管理單例的邏輯分開,這裏引入代理類來優化代碼

var CreateDiv=function(html) {
    this.html = html;
    this.init();
}
CreateDiv.proptype.init=function(){
    var div=document.createElement('div');
    div.innerHTML=this.html;
    document.body.appendChild(div);
};


var ProxySingleTonCreateDiv=(function() {
    var instance;
    return function (html) {
        if (!instance) {
            instance = new CreateDiv(html);
        }

        return instance;
    };
})()


var a=new CreateDiv('sven1');
var b=new CreateDiv('sven2');
複製代碼

Javascript中實現

JavaScript是無類的語言,在JavaScript中,咱們經過全局變量來實現單例模式,可是全局變量常常帶來命名污染的問題,解決方案以下:

1.使用命名空間

var namespace1={
    a:function(){
        alert(1);
    },
    b:function(){
        laert(2);
    }
}
複製代碼

2.使用閉包封裝私有變量

var user=(function(){
    var _name='sven',_age=29;
    return{
        getUserInfo:function(){
            return _name+'-'+'_age';
        }
    }
})
複製代碼

中介者模式

中介者模式是指對象和對象之間藉助第三方中介者進行通訊.中介模式使各個對象之間得以解耦,以中介者和對象之間的一對多關係取代了對象之間的網狀多對多關係。各個對象只需關注自身的實現,對象之間的交互關係交給了中介者來實現和維護。

一場測試結束後, 公佈結果: 告知解答出題目的人挑戰成功, 不然挑戰失敗。

const player = function(name) {
  this.name = name
  playerMiddle.add(name)
}

player.prototype.win = function() {
  playerMiddle.win(this.name)
}

player.prototype.lose = function() {
  playerMiddle.lose(this.name)
}

//在這段代碼中 A、B、C 之間沒有直接發生關係, 而是經過另外的 playerMiddle 對象創建連接, 姑且將之當成是中介者模式了
const playerMiddle = (function() { 
  const players = []
  const winArr = []
  const loseArr = []
  return {
    add: function(name) {
      players.push(name)
    },
    win: function(name) {
      winArr.push(name)
      if (winArr.length + loseArr.length === players.length) {
        this.show()
      }
    },
    lose: function(name) {
      loseArr.push(name)
      if (winArr.length + loseArr.length === players.length) {
        this.show()
      }
    },
    show: function() {
      for (let winner of winArr) {
        console.log(winner + '挑戰成功;')
      }
      for (let loser of loseArr) {
        console.log(loser + '挑戰失敗;')
      }
    },
  }
}())

const a = new player('A 選手')
const b = new player('B 選手')
const c = new player('C 選手')

a.win()
b.win()
c.lose()
複製代碼

適配器模式

適配器模式用來解決兩個軟件實體的接口不兼容的問題

var googleMap = {
		show: function(){
			console.log( '開始渲染谷歌地圖' );
		}
	};
	var baiduMap = {
		display: function(){
			console.log( '開始渲染百度地圖' );
		}
	};

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



  //解決百度地圖中展現地圖的方法名與谷歌地圖中展現地圖的方法名不同的問題
	var baiduMapAdapter = {
		show: function(){
			return baiduMap.display();

		}
	};

	renderMap( googleMap ); // 輸出:開始渲染谷歌地圖
	renderMap( baiduMapAdapter ); // 輸出:開始渲染百度地圖
複製代碼

迭代器模式

迭代器是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。迭代器模式能夠把迭代的過程從業務邏輯中分離出來,在使用迭代器模式以後,即便不關心對象的內部構造,也能夠按順序訪問其中的每一個元素。

迭代器的實現:

var each=function(arry,callback){
    for(var i=0,l=ary.length;i<l;i++) {
        callback.call(ary[i], i, ary[i]);
    }
}

each([1,2,3],function(i,n){
    alert([i,n]);
})
複製代碼

內部迭代器

上面的each函數即便內部迭代器,因爲內部迭代器的迭代規則已經被提早規定,上面的each函數就沒法同時迭代2個數組了,不如如今若是咱們要實現判斷2個數組裏面的元素的值是否徹底相等,若是不能修改each函數自己的話就只能修改each的回調函數了

var compare=function(ary1,ary2){
    if(ary1.length!=ary2.length){
        throw new Error("ary1和ary2不相等")
    }
    each(ary1,funtion(i,n){
        if(n!==ary2[i]){
            thorw new Error('ary1和ary2不相等')
        }
    })
    alert('ary1和ary2相等')
}

compare([1,2,3],[1,2,4]);
複製代碼

外部迭代器

外部迭代器必須顯式的請求迭代下一個元素,外部迭代器增長了一些調用的複雜度,但相對也加強了迭代器的靈活性,咱們能夠手工控制迭代的過程或順序

var Interator=function(obj){
    var current=0;
    var next=funtion(){
        current+=1;
    }
    var isDone=function(){
        return current>=obj.length;
    }
    var getCurrItem=function(){
        return obj[current];
    }
    return {
        next:next,
        isDone:isDone,
    getCurrItem:getCurrItem,
        length:obj.length
    }
}
複製代碼

組合模式

  • 組合模式在對象間造成樹形結構;

  • 組合模式中基本對象和組合對象被一致對待;

  • 無須關心對象有多少層, 調用時只需在根部進行調用;

    掃描文件夾時, 文件夾下面能夠爲另外一個文件夾也能夠爲文件, 咱們但願統一對待這些文件夾和文件, 這種情形適合使用組合模式。

const Folder = function(folder) {
  this.folder = folder
  this.lists = []
}

Folder.prototype.add = function(resource) {
  this.lists.push(resource)
}

Folder.prototype.scan = function() {
  console.log('開始掃描文件夾: ', this.folder)
  for (let i = 0, folder; folder = this.lists[i++];) {
    folder.scan()
  }
}

const File = function(file) {
  this.file = file
}

File.prototype.add = function() {
  throw Error('文件下不能添加其它文件夾或文件')
}

File.prototype.scan = function() {
  console.log('開始掃描文件: ', this.file)
}

const folder = new Folder('根文件夾')
const folder1 = new Folder('JS')
const folder2 = new Folder('life')

const file1 = new File('深刻React技術棧.pdf')
const file2 = new File('JavaScript權威指南.pdf')
const file3 = new File('小王子.pdf')

folder1.add(file1)
folder1.add(file2)

folder2.add(file3)

folder.add(folder1)
folder.add(folder2)

folder.scan()

// 開始掃描文件夾: 根文件夾
// 開始掃描文件夾: JS
// 開始掃描文件: 深刻React技術棧.pdf
// 開始掃描文件: JavaScript權威指南.pdf
// 開始掃描文件夾: life
// 開始掃描文件: 小王子.pdf
複製代碼

享元模式

享元模式是一種優化程序性能的模式, 本質爲減小對象建立的個數。

如下狀況可使用享元模式:

  1. 有大量類似的對象, 佔用了大量內存
  2. 對象中大部分狀態能夠抽離爲外部狀態

某商家有 50 種男款內衣和 50 種款女款內衣, 要展現它們

方案一: 造 50 個塑料男模和 50 個塑料女模, 讓他們穿上展現, 代碼以下:

const Model = function(gender, underwear) {
  this.gender = gender
  this.underwear = underwear
}

Model.prototype.takephoto = function() {
  console.log(`${this.gender}穿着${this.underwear}`)
}

for (let i = 1; i < 51; i++) {
  const maleModel = new Model('male', `第${i}款衣服`)
  maleModel.takephoto()
}

for (let i = 1; i < 51; i++) {
  const female = new Model('female', `第${i}款衣服`)
  female.takephoto()
}
複製代碼

方案二: 造 1 個塑料男模特 1 個塑料女模特, 分別試穿 50 款內衣

const Model = function(gender) {
  this.gender = gender
}

Model.prototype.takephoto = function() {
  console.log(`${this.sex}穿着${this.underwear}`)
}

const maleModel = new Model('male')
const femaleModel = new Model('female')

for (let i = 1; i < 51; i++) {
  maleModel.underwear = `第${i}款衣服`
  maleModel.takephoto()
}

for (let i = 1; i < 51; i++) {
  femaleModel.underwear = `第${i}款衣服`
  femaleModel.takephoto()
}
複製代碼

對比發現: 方案一建立了 100 個對象, 方案二隻建立了 2 個對象, 在該 demo 中, gender(性別) 是內部對象, underwear(穿着) 是外部對象。

固然在方案二的 demo 中, 還能夠進一步改善:

  1. 一開始就經過構造函數顯示地建立實例, 可用工場模式將其升級成可控生成
  2. 在實例上手動添加 underwear 不是很優雅, 能夠在外部單獨在寫個 manager 函數
const Model = function(gender) {
  this.gender = gender
}

Model.prototype.takephoto = function() {
  console.log(`${this.gender}穿着${this.underwear}`)
}

const modelFactory = (function() { // 優化第一點
  const modelGender = {}
  return {
    createModel: function(gender) {
      if (modelGender[gender]) {
        return modelGender[gender]
      }
      return modelGender[gender] = new Model(gender)
    }
  }
}())

const modelManager = (function() {
  const modelObj = {}
  return {
    add: function(gender, i) {
      modelObj[i] = {
        underwear: `第${i}款衣服`
      }
      return modelFactory.createModel(gender)
    },
    copy: function(model, i) { // 優化第二點
      model.underwear = modelObj[i].underwear
    }
  }
}())

for (let i = 1; i < 51; i++) {
  const maleModel = modelManager.add('male', i)
  modelManager.copy(maleModel, i)
  maleModel.takephoto()
}

for (let i = 1; i < 51; i++) {
  const femaleModel = modelManager.add('female', i)
  modelManager.copy(femaleModel, i)
  femaleModel.takephoto()
}
複製代碼

模板方法模式

模板方法是一種基於繼承的設計模式,模板方法模式有兩個部分組成,第一個是抽象父類,第二個是具體的實現子類。但在JavaScript中,咱們不少時候都不須要畫瓢同樣去實現一個模板方法模式,高階函數是更好的選擇。

var Coffee = function(){};
	Coffee.prototype.boilWater = function(){
		console.log( '把水煮沸' );
	};
	Coffee.prototype.brewCoffeeGriends = function(){
		console.log( '用沸水沖泡咖啡' );
	};
	Coffee.prototype.pourInCup = function(){
		console.log( '把咖啡倒進杯子' );
	};
	Coffee.prototype.addSugarAndMilk = function(){
		console.log( '加糖和牛奶' );
	};
	Coffee.prototype.init = function(){
		this.boilWater();
		this.brewCoffeeGriends();
		this.pourInCup();
		this.addSugarAndMilk();
	};
	var coffee = new Coffee();
	coffee.init();

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

泡茶和泡咖啡的過程當中有幾個是同樣的,咱們能夠定義一個Beverage類,定義泡茶和泡咖啡中的共同動做,而泡茶和泡咖啡中獨有的動做能夠直接重寫Beverage類中的方法便可

var Beverage = function( param ){
		var boilWater = function(){
			console.log( '把水煮沸' );
		};
		var brew = param.brew || function(){
			throw new Error( '必須傳遞brew 方法' );
		};
		var pourInCup = param.pourInCup || function(){
			throw new Error( '必須傳遞pourInCup 方法' );
		};
		var addCondiments = param.addCondiments || function(){
			throw new Error( '必須傳遞addCondiments 方法' );
		};
		var F = function(){};
		F.prototype.init = function(){
			boilWater();
			brew();
			pourInCup();
			addCondiments();
		};
		return F;
	};
	var Coffee = Beverage({
		brew: function(){
			console.log( '用沸水沖泡咖啡' );
		},
		pourInCup: function(){
			console.log( '把咖啡倒進杯子' );
		},
		addCondiments: function(){
			console.log( '加糖和牛奶' );
		}
	});

	var Tea = Beverage({
		brew: function(){
			console.log( '用沸水浸泡茶葉' );
		},
		pourInCup: function(){
			console.log( '把茶倒進杯子' );
		},
		addCondiments: function(){
			console.log( '加檸檬' );
		}
	});
	var coffee = new Coffee();
	coffee.init();
	var tea = new Tea();
	tea.init();
複製代碼

命令模式

咱們把某次任務分紅兩個部分,一部分程序員實現繪製按鈕,他們不知道按鈕用來幹什麼,一部分程序員負責編寫點擊按鈕後的具體行爲。

<body>

	<button id="button1">點擊按鈕1</button>
	<button id="button2">點擊按鈕2</button>
	<button id="button3">點擊按鈕3</button>

	<script>
		var button1 = document.getElementById( 'button1' ),
		var button2 = document.getElementById( 'button2' ),
		var button3 = document.getElementById( 'button3' );

		var setCommand = function( button, command ){
			button.onclick = function(){
				command.execute();
			}
		};

		var MenuBar = {
			refresh: function(){
				console.log( '刷新菜單目錄' );
			}
		};
		var SubMenu = {
			add: function(){
				console.log( '增長子菜單' );
			},
			del: function(){
				console.log( '刪除子菜單' );
			}
		};
		在讓button 變得有用起來以前,咱們要先把這些行爲都封裝在命令類中:
		var RefreshMenuBarCommand = function( receiver ){
			this.receiver = receiver;
		};
		RefreshMenuBarCommand.prototype.execute = function(){
			this.receiver.refresh();
		};
		var AddSubMenuCommand = function( receiver ){
			this.receiver = receiver;
		};

		AddSubMenuCommand.prototype.execute = function(){
			this.receiver.add();
		};
		var DelSubMenuCommand = function( receiver ){
			this.receiver = receiver;
		};
		DelSubMenuCommand.prototype.execute = function(){
			console.log( '刪除子菜單' );
		};

		var refreshMenuBarCommand = new RefreshMenuBarCommand( MenuBar );
		var addSubMenuCommand = new AddSubMenuCommand( SubMenu );
		var delSubMenuCommand = new DelSubMenuCommand( SubMenu );
		setCommand( button1, refreshMenuBarCommand );
		setCommand( button2, addSubMenuCommand );
		setCommand( button3, delSubMenuCommand );
	</script>
</body>
複製代碼

參考資料:

《JavaScript設計模式與開發實踐》

相關文章
相關標籤/搜索