讓你的 JS 代碼變得干凈優(yōu)雅且可維護(hù)
眾所周知,魔法是這樣的:

哦,不是。。
在編程的世界里也有魔法,一般稱(chēng)其為:魔法數(shù)字,魔法變量,魔法字符串。例如這樣:
const a = await abcdefg();
console.log(a === 200);
const b = await asdfgh();
if (b === 0) {
} else if (b === 1) {
} else if (b === 2) {};
for (let i = 0; i < 10; i++) {};
以上直接出現(xiàn)的,莫名其妙的變量名,字符串以及判斷條件數(shù)字,就叫魔法。。。
這種寫(xiě)法寫(xiě)出來(lái)的代碼晦澀難懂,難以維護(hù),隱藏 BUG 多,除非你準(zhǔn)備給接手的人埋坑,或者準(zhǔn)備辭職,不然千萬(wàn)別這么寫(xiě)(容易被打斷腿,????? )
那么怎么寫(xiě)才更優(yōu)雅?
語(yǔ)義化
首先便是語(yǔ)義化。一個(gè)是變量,常量的語(yǔ)義化,例如:
const SUCCESS_STATUS = 200;
const requestStatus = await getStatus();
console.log(requestStatus === SUCCESS_STATUS);
const userRole = await getUserRole();
const GUEST_CODE = 0;
const USER_CODE = 1;
const ADMIN_CODE = 2;
if (userRole === GUEST_CODE) {
} else if (userRole === USER_CODE) {
} else if (userRole === ADMIN_CODE) {};
const MAX_NUM = 10;
const MIN_NUM = 0;
for (let currentNum = MIN_NUM; currentNum < MAX_NUM; currentNum++) {};
一般的規(guī)則就是變量用小寫(xiě),常量用大寫(xiě),把變量名語(yǔ)義化,那么當(dāng)你看到這段代碼的時(shí)候,一眼就能知道它是做什么的,而不是非得要浪費(fèi)時(shí)間看完上下文,或者是猜。
枚舉
對(duì)于上面判斷 userRole 的代碼,其實(shí)我們可以用更優(yōu)雅的方式去實(shí)現(xiàn),那就是 枚舉 。
按照維基百科的說(shuō)明:在數(shù)學(xué)和計(jì)算機(jī)科學(xué)理論中,一個(gè)集的枚舉是列出某些有窮序列集的所有成員的程序,或者是一種特定類(lèi)型對(duì)象的計(jì)數(shù)。這兩種類(lèi)型經(jīng)常(但不總是)重疊。
其實(shí)就是組織收集有關(guān)聯(lián)變量的一種方式。枚舉的好處在于方便多狀態(tài)的管理,以及可讀性更強(qiáng)。例如:
const ROLES = {
GUEST: 0,
USER: 1,
ADMIN: 2
};
const userRole = await getUserRole();
if (userRole === ROLES.GUEST) {
} else if (userRole === ROLES.USER) {
} else if (userRole === ROLES.ADMIN) {};
通過(guò)枚舉的方式歸納起來(lái),維護(hù)起來(lái)更方便,而且要添加狀態(tài)直接在 ROLES 對(duì)象里寫(xiě)就行,更方便快捷。
策略模式
維基百科上說(shuō):策略模式作為一種軟件設(shè)計(jì)模式,指對(duì)象有某個(gè)行為,但是在不同的場(chǎng)景中,該行為有不同的實(shí)現(xiàn)算法。
上面的代碼依舊是可優(yōu)化的,在這里我們可以利用策略模式來(lái)做進(jìn)一層的優(yōu)化。
具體的例子就是如下:
const ROLES = {
GUEST: 0,
USER: 1,
ADMIN: 2
};
const ROLE_METHODS = {
[ROLES.GUEST]() {},
[ROLES.USER]() {},
[ROLES.ADMIN]() {},
};
const userRole = await getUserRole();
ROLE_METHODS[userRole]();
通過(guò)上面的寫(xiě)法,我們可以知道,當(dāng)我們需要增加角色,或者修改角色數(shù)字的時(shí)候,只需要修改 ROLES 里對(duì)應(yīng)的字段,以及 ROLE_METHODS 里的方法即可,這樣我們就可以將可能很冗長(zhǎng)的 if...else 代碼給抽離出來(lái),顆粒度更細(xì),更好維護(hù)。
更在狀態(tài)
除了上面的方式之外,我們還可以利用“ 狀態(tài) ”的概念來(lái)寫(xiě)代碼。在看代碼之前,我們先了解下什么是 “有限狀態(tài)機(jī)”。
根據(jù)維基百科的解釋?zhuān)河邢逘顟B(tài)機(jī)(英語(yǔ):finite-state machine,縮寫(xiě):FSM)又稱(chēng)有限狀態(tài)自動(dòng)機(jī)(英語(yǔ):finite-state automation,縮寫(xiě):FSA),簡(jiǎn)稱(chēng)狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)計(jì)算模型。
例如我們熟悉的 Promise ,它就是在狀態(tài)集:PENDIN 、 FULFILLED 、 REJECTED 之間單向流轉(zhuǎn)的有限狀態(tài)機(jī)。
狀態(tài)機(jī)的概念跟策略模式類(lèi)似,實(shí)現(xiàn)方式也類(lèi)似,這里面最大的不同是在于 “語(yǔ)義” 。
策略模式更適合于互不依賴(lài),同時(shí)只能存在一個(gè)狀態(tài)的場(chǎng)景,例如:
const 吃 = {
沙縣大酒店() {
吃云吞()
},
開(kāi)封菜() {
吃漢堡()
},
在家() {
吃外賣(mài)()
}
};
這里面如果我們肚子餓了,就只能在 沙縣大酒店() , 開(kāi)封菜() , 在家() 這幾個(gè)狀態(tài)里選。
你不能都吃,當(dāng)然以下情況除外。。。

如果是狀態(tài)模式,則會(huì)有這種情況:
const 打工人 = {
起床() {},
上班() {},
加班() {},
下班() {}
};
// 早上6點(diǎn)
打工人.起床();
// 早上9點(diǎn)
打工人.上班();
// 晚上6點(diǎn)
打工人.加班();
// 晚上12點(diǎn)
打工人.下班();
這里的打工人根據(jù)不同的時(shí)間,進(jìn)行不同的任務(wù),便是打工人模式,哦不,狀態(tài)模式。這里的時(shí)間就是狀態(tài)。
我們舉個(gè)實(shí)際的業(yè)務(wù)例子,就是訂單列表頁(yè),通常我們的訂單可能有這幾種狀態(tài):

不同的狀態(tài)展示的 UI 也不同,所以我們以不同的狀態(tài)劃分好模塊之后,代碼寫(xiě)起來(lái)就會(huì)清晰很多,我們以 Vue 代碼為例:
// contants.js
export const ORDER_STATUS = {
INIT: 0, // 初始化
CREATED: 1, // 訂單創(chuàng)建
ARREARAGE: 2, // 待支付
PURCHASED: 3, // 已購(gòu)買(mǎi)
SHIPPED: 4, // 已發(fā)貨
COMPLETED: 5 // 已完成
};
// order.vue
<template>
<div>
<section v-if="orderIsInit"></section>
<section v-if="orderIsCreated"></section>
<section v-if="orderIsArrearage"></section>
<section v-if="orderIsPurchased"></section>
<section v-if="orderIsShipped"></section>
<section v-if="orderIsCompleted"></section>
</div>
</template>
<script>
import ORDER_STATUS from './contants';
import eq from 'lodash';
export default {
computed: {
/**
* @func
* @name orderIsInit
* @desc 判斷訂單是否初始化的狀態(tài)
* @returns {string} 判斷訂單是否初始化的狀態(tài)
*/
orderIsInit() {
return eq(this.orderStatus, ORDER_STATUS.INIT);
},
/**
* @func
* @name orderIsCreated
* @desc 判斷訂單是否已創(chuàng)建的狀態(tài)
* @returns {string} 訂單是否已創(chuàng)建
*/
orderIsCreated() {
return eq(this.orderStatus, ORDER_STATUS.CREATED);
},
/**
* @func
* @name orderIsArrearage
* @desc 判斷訂單是否未付款的狀態(tài)
* @returns {string} 訂單是否未付款
*/
orderIsArrearage() {
return eq(this.orderStatus, ORDER_STATUS.ARREARAGE);
},
/**
* @func
* @name orderIsPurchased
* @desc 判斷訂單是否已購(gòu)買(mǎi)的狀態(tài)
* @returns {string} 訂單是否已購(gòu)買(mǎi)
*/
orderIsPurchased() {
return eq(this.orderStatus, ORDER_STATUS.PURCHASED);
},
/**
* @func
* @name orderIsShipped
* @desc 判斷訂單是否已發(fā)貨的狀態(tài)
* @returns {string} 訂單是否已發(fā)貨
*/
orderIsShipped() {
return eq(this.orderStatus, ORDER_STATUS.SHIPPED);
},
/**
* @func
* @name orderIsCompleted
* @desc 判斷訂單是否已完成的狀態(tài)
* @returns {string} 訂單是否已完成
*/
orderIsCompleted() {
return eq(this.orderStatus, ORDER_STATUS.COMPLETED);
},
},
data() {
return {
orderStatus: ORDER_STATUS.INIT // 訂單狀態(tài)
}
},
methods: {
/**
* @func
* @name getOrderStatus
* @desc 判斷訂單狀態(tài)
* @returns {string} 返回當(dāng)前訂單狀態(tài)
*/
async getOrderStatus() {}
},
async created() {
this.orderStatus = await this.getOrderStatus();
}
}
</script>
將頁(yè)面組件按狀態(tài)劃分,實(shí)現(xiàn)獨(dú)立自治,這樣子既能防止代碼耦合,方便維護(hù) debug,也方便開(kāi)發(fā)者自測(cè),如果需要看不同狀態(tài)的展示效果,只要手動(dòng)給 orderStatus 賦值即可,方便快捷。
面向切面
按照維基百科的解釋?zhuān)好嫦蚯忻娴某绦蛟O(shè)計(jì)(Aspect-oriented programming,AOP,又譯作面向方面的程序設(shè)計(jì)、剖面導(dǎo)向程序設(shè)計(jì))是計(jì)算機(jī)科學(xué)中的一種程序設(shè)計(jì)思想,旨在將橫切關(guān)注點(diǎn)與業(yè)務(wù)主體進(jìn)行進(jìn)一步分離,以提高程序代碼的模塊化程度。
上面這段文字估計(jì)沒(méi)有什么人看,算了,直接上代碼吧

我們看回上面打工人的場(chǎng)景,假定老板想要知道打工人每個(gè)狀態(tài)開(kāi)始前跟結(jié)束前的時(shí)間以及做點(diǎn)什么,那么該怎么做呢?這個(gè)時(shí)候我們不難想到可以直接往狀態(tài)函數(shù)里寫(xiě)代碼,例如:
const 打工人 = {
起床() {
老板.start();
打工人.do();
老板.end();
},
上班() {
老板.start();
打工人.do();
老板.end();
},
加班() {
老板.start();
打工人.do();
老板.end();
},
下班() {
老板.start();
打工人.do();
老板.end();
}
};
// 早上6點(diǎn)
打工人.起床();
// 早上9點(diǎn)
打工人.上班();
// 晚上6點(diǎn)
打工人.加班();
// 晚上12點(diǎn)
打工人.下班();
但是這樣打工人一下子就察覺(jué)到到了老板在監(jiān)控他的生活,如果要做到不被人察覺(jué)(不影響業(yè)務(wù)邏輯),那我們既可以采用 AOP 的實(shí)現(xiàn)方式。代碼如下:
import eq from 'lodash';
const TYPES = {
FUNCTION: 'function'
}
const 老板監(jiān)控中的打工人 = new Proxy(打工人, {
get(target, key, value, receiver) {
console.log('老板開(kāi)始看你了~');
const res = Reflect.get(target, key, value, receiver);
const 打工人任務(wù) = eq(typeof res, TYPES.FUNCTION) ? res() : res;
console.log('老板開(kāi)始記你小本本了~');
return () => 打工人任務(wù);
}
});
所以我們可以看到以下結(jié)果:

這樣子,我們就可以輕松簡(jiǎn)單地監(jiān)控到了打工人每天干的活,而且還不讓打工人發(fā)現(xiàn),簡(jiǎn)直是資本家聽(tīng)了都流淚呀。
最后
如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

