職責(zé)鏈的定義:使多個對象都有機(jī)會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象能處理它為止,傳遞鏈中的這些對象就叫節(jié)點(diǎn)。
需求背景: 一個電商網(wǎng)站,用戶交500定金且定金已付時,可享受500優(yōu)惠券且不受貨物數(shù)量限制;用戶交200定金且定金已付時,可享受500優(yōu)惠券且不受貨物數(shù)量限制;用戶不交定金時受貨物數(shù)量限制,有貨時原價買,無貨時則無法買。
原始版本, ?if else
?一路判斷
var buyOrder = function(orederType, pay, stock){
if(orederType == 1){
if(pay){
console.log('500優(yōu)惠券');
}else {
if(stock > 0){
console.log('普通購物頁面');
}else {
console.log('已無貨');
}
}
}else if(orederType == 2){
if(pay){
console.log('200優(yōu)惠券');
}else {
if(stock > 0){
console.log('普通購物頁面');
}else {
console.log('已無貨');
}
}
}else if(orederType == 3){
if(stock > 0){
console.log('普通購物頁面');
}else {
console.log('已無貨');
}
}
}
buyOrder(1, true, 600)
改進(jìn)版本
var order500 = function(orderType, pay , stock){
if(orderType == '1' && pay == true){
console.log('500優(yōu)惠券');
}else {
order200(orderType, pay , stock)
}
}
var order200 = function(orderType, pay , stock){
if(orderType == '2' && pay == true){
console.log('200優(yōu)惠券');
}else {
orderNormal(orderType, pay , stock)
}
}
var orderNormal = function(orderType, pay , stock){
if(stock > 0){
console.log('普通購物頁面');
}else {
console.log('已無貨');
}
}
order500(3, true, 0)
優(yōu)化版本1:
同步的職責(zé)鏈
//3個訂單函數(shù) ,它們都是節(jié)點(diǎn)函數(shù)
var order500 = function(orderType, pay , stock){
if(orderType == '1' && pay == true){
console.log('500優(yōu)惠券');
}else {
return 'nextSuccessor'; //我不知道下個節(jié)點(diǎn)是誰,反正把請求往后傳遞
}
}
var order200 = function(orderType, pay , stock){
if(orderType == '2' && pay == true){
console.log('200優(yōu)惠券');
}else {
return 'nextSuccessor'; //我不知道下個節(jié)點(diǎn)是誰,反正把請求往后傳遞
}
}
var orderNormal = function(orderType, pay , stock){
if(stock > 0){
console.log('普通購物頁面');
}else {
console.log('已無貨');
}
}
//職責(zé)構(gòu)造函數(shù)
var Chain = function(fn){
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function(successor){ //設(shè)置職責(zé)順序方法
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;
}
//把3個訂單函數(shù)分別包裝成職責(zé)鏈的節(jié)點(diǎn)
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)
//然后指定節(jié)點(diǎn)在職責(zé)鏈中的順序
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)
//最后把請求傳遞給第一個節(jié)點(diǎn),開啟職責(zé)鏈模式傳遞
chainOrder500.passRequest(1, true, 500) //500優(yōu)惠券
chainOrder500.passRequest(3, true, 20) //普通購物頁面
chainOrder500.passRequest(3, true, 0) //已無貨
//此時如果中間有需求改動,只需如此做:
var order300 = function(){
if(orderType == '3' && pay == true){
console.log('300優(yōu)惠券');
}else {
return 'nextSuccessor'; //我不知道下個節(jié)點(diǎn)是誰,反正把請求往后傳遞
}
}
var chainOrder300 = new Chain(order300) //添加新職責(zé)節(jié)點(diǎn)
chainOrder500.setNextSuccessor(chainOrder300)
chainOrder300.setNextSuccessor(chainOrder300) //修改職責(zé)鏈順序
chainOrder200.setNextSuccessor(chainOrderNormal)
//這樣,就可以完全不必去理會原來的訂單函數(shù)代碼,只需增加一個節(jié)點(diǎn),然后重新設(shè)置職責(zé)鏈中的相關(guān)節(jié)點(diǎn)的順序就行。
優(yōu)化版本2:異步的職責(zé)鏈
在實(shí)際開發(fā)中,經(jīng)常會遇到 一些異步的問題,比如要在節(jié)點(diǎn)函數(shù)中發(fā)起一個ajax請求,異步請求返回的結(jié)果才能決定是否繼續(xù)在職責(zé)鏈中passRequest
可以給Chain類再增加一個原型方法:
//職責(zé)構(gòu)造函數(shù)
var Chain = function(fn){
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function(successor){ //設(shè)置職責(zé)順序方法
this.successor = successor
}
Chain.prototype.passRequest = function(){ //請求傳遞
var ret = this.fn.apply(this, arguments)
if(ret === 'nextSuccessor'){ //傳遞給職責(zé)鏈中的下一個節(jié)點(diǎn)
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
return ret;
}
//新增,表示手動傳遞請求給職責(zé)鏈中的下一個節(jié)點(diǎn)
Chain.prototype.next = function(){
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
//異步職責(zé)鏈例子
var fn1 = new Chain(function(){
console.log(1);
return 'nextSuccessor'
})
var fn2 = new Chain(function(){
console.log(2);
var self = this;
setTimeout(function(){
self.next()
}, 1000)
})
var fn3 = new Chain(function(){
console.log(3);
})
//指定節(jié)點(diǎn)在職責(zé)鏈中的順序
fn1.setNextSuccessor(fn2)
fn2.setNextSuccessor(fn3)
//把請求傳遞給第一個節(jié)點(diǎn),開始節(jié)點(diǎn)傳遞
fn1.passRequest()
//輸出 1 2 ...(1秒后)... 3
//這是一個異步職責(zé)鏈,請求在職責(zé)鏈節(jié)點(diǎn)中傳遞,但節(jié)點(diǎn)有權(quán)利決定什么時候 把請求交給下一個節(jié)點(diǎn)。這樣可以創(chuàng)建一個異步ajax隊(duì)列庫。
tips:
這里補(bǔ)充個知識點(diǎn):“短路求值” ?&&
? 會返回第一個假值?(0, null, "", undefined, NaN)
?,而? ||
?則會返回第一個真值。
?var x = a || b || c
? 等價于:
var x;
if(a){
x = a;
} else if(b){
x = b;
} else {
x = c;
}
var x = a && b && c
等價于:
var x = a;
if(a){
x = b;
if(b){
x = c;
}
}
所以 ?&&
? 有時候會用來代替 ?if (expression) doSomething()
?,轉(zhuǎn)成 ?&&
?方式就是 ?expression && doSomething()
?。
而 ?||
? 比較用來在函數(shù)中設(shè)置默認(rèn)值,比如:
function doSomething(arg1, arg2, arg3) {
arg1 = arg1 || 'arg1Value';
arg2 = arg2 || 'arg2Value';
}
不過還需要看具體的使用場景,就比如如果要求 ?doSomething()
? 傳入的 arg1 為一個數(shù)值,則上面的寫法就會出現(xiàn)問題(在傳入 0 的時候被認(rèn)為是一個假值而使用默認(rèn)值)。
現(xiàn)在個人比較常用的方法只判斷是否與 ?undefined
? 相等,比如
function doSomething(arg) {
arg = arg !== void 0 ? arg : 0;
}
職責(zé)鏈模式的優(yōu)勢:解耦請求發(fā)送者和 N 個接收者之間的復(fù)雜關(guān)系,由于不知道鏈條中的哪個節(jié)點(diǎn)可以處理你發(fā)出的請求,所以只需把請求傳遞給第一個節(jié)點(diǎn)就行。
如果在實(shí)際開發(fā)中,當(dāng)維護(hù)一個含有多個條件分支語句的巨大函數(shù)時時,可以使用職責(zé)鏈模式。鏈中的節(jié)點(diǎn)對象可以靈活拆分重組,增加刪除節(jié)點(diǎn),且無需改動其他節(jié)點(diǎn)函數(shù)內(nèi)的代碼。