一、背景
在产品快速迭代过程中,由于追求开发速度,忽略代码的可读性与扩展性,不合理的使用if-else条件判断会使我们的程序复杂度大大提升,同时也会使代码的可读性急速下降,后期维护难度也大大提高。
二、目的
优化是为了让代码可读性、可维护性更好;
- 对老业务:在合适的基础上适当优化,核心是不影响已有功能
- 对新业务:选择合适的策略,尽可能让 if 层级少
三、优化方案
3.1 单个 if 语句优化策略
3.1.1 逻辑与运算符
好处:代码在一行很清晰,简洁好读
// 优化前
if( falg ){
someMethod()
}
// 优化后
falg && someMethod();
3.2 多个if语句优化策略
3.2.1 逻辑与取反逻辑
// 优化前
if (user && password) {
// 逻辑处理
} else {
throw('用户名和密码不能为空!')
}
// 优化后
if (!user || !password) return throw('用户名和密码不能为空!')
// 逻辑处理
function handleEvent() {
if (A) {
doA
if (B) {
doB
}
}
}
function handleEvent() {
if (!A || !B) {
return;
}
...
}
3.2.2 合并条件表达式
[解释]多个条件检查对应点的执行功能函数一致时,通过逻辑或把条件检查合并成一个表达式
// 优化前
function getVipDiscount() {
if(age<18){
return 0.8;
}
if("深圳".equals(city)){
return 0.8;
}
if(isStudent){
return 0.8;
}
//do somethig
}
// 优化后
function getVipDiscount(){
if(age<18|| "深圳".equals(city)||isStudent){
return 0.8;
}
//doSomthing
}
3.2.3 合并条件片段
// 优化前:每个条件分支有一些重复代码
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
jump( currPage ); // 跳转
}else if ( currPage >= totalPage ){
currPage = totalPage;
jump( currPage ); // 跳转
}else{
jump( currPage ); // 跳转
}
};
//优化后:将分支重复的代码独立出来
var paging = function( currPage ){
if ( currPage <= 0 ){
currPage = 0;
}else if ( currPage >= totalPage ){
currPage = totalPage;
}
jump( currPage ); // 把jump 函数独立出来
};
3.3 单个 if else 语句优化策略
3.3.1 排非提前return策略
[解释]排非策略,先排除不满足大多数业务情况的 false 的情形,通过后再执行为 true 时的业务逻辑。
好处:可以干掉else,减少代码分支,提高代码的可维护性和可阅读性。
// 优化前
if(condition){
//doSomething
}else{
return ;
}
//优化后
if(!condition){
return ;
}
//doSomething
3.3.2 使用三元运算符精简代码,提升可读性
case1: 优化前 VS 优化后
case2: 三元条件赋值
// 优化前
if( offsetTop < 0 ){
this.titleFixed = true
} else {
this.titleFixed = false
}
// 优化后
// 改成三元
(offsetTop < 0) ? this.titleFixed = true : this.titleFixed = false;
// 条件块里面的赋值情况是布尔值,所以可以更简化成:
this.titleFixed = offsetTop < 0;
3.4 多个 if else 嵌套优化策略
// “金字塔一样的if else嵌套”
if (result.data) {
if (result.data.userInfo) {
if (Array.isArray(result.data.userInfo.hobby)) {
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
} else {
return "hobby字段为空";
}
} else {
return "hobby字段不是一个数组";
}
} else {
return "userInfo字段不存在";
}
} else {
return "data字段不存在";
}
3.4.1 异常条件提前返回
[解释] 尽可能地维持正常流程代码在最外层,而不是在if内部,逻辑更清晰,一目了然,没有 if-else 嵌套难以理解的流程。
[Tips] 把外层if表达式进行反转
if (!result.data) return "data字段不存在";
if (!result.data.userInfo) return "userInfo字段不存在";
if (!Array.isArray(result.data.userInfo.boddy)) return "hobby字段不是一个数组";
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
}
3.4.2 采取的try catch策略
try {
// 有可能出现错误的代码写在这里
if (result.data.userInfo.hobby.length) {
// 进行业务逻辑操作
}
} catch (error) {
// 出错后的处理写在这里
}
3.4.3 可选链优化
// 可选链优化
if (result?.data?.userInfo?.hobby?.length) {
// 进行业务逻辑操作
}
3.5 多个else if分支优化
多个else if条件判断代码,随着逻辑复杂性的增加,else if的代码将变得越来越肿,导致重构困难。
if (this.type === 'A') {
this.handleA();
} else if (this.type === 'B') {
this.handleB();
} else if (this.type === 'C') {
this.handleC();
} else if (this.type === 'D') {
this.handleD();
} else {
this.handleE();
}
3.5.1 switch-case
好处:不同的条件分支之间没有嵌套,并且彼此独立, 逻辑很清楚。
switch(val){
case 'A':
handleA()
break
case 'B':
handleB()
break
case 'C':
handleC()
break
case 'D':
handleD()
break
}
3.5.2 Key-value 对象枚举
好处:将条件与特定操作相关联的键值,减少代码体积
let enums = {
'A': handleA,
'B': handleB,
'C': handleC,
'D': handleD,
'E': handleE
}
function action(val){
let handleType = enums[val]
handleType()
}
3.5.3 Map构建键/值对
let enums = new Map([
['A', handleA],
['B', handleB],
['C', handleC],
['D', handleD],
['E', handleE]
])
function action(val){
let handleType = enums(val)
handleType()
}
对比Object的话,Map具有许多优点
对象的键只能是字符串或符号,而Map的键可以是任何类型的值。
我们可以使用Map size属性轻松获取Map的键/值对的数量,而对象的键/值对的数量只能手动确定。
具有极快的查找速度。
多层复杂条件,Map语句优势就更明显了:
if (mode == 'kline') {
if (this.type === 'A') {
this.klineA()
} else if (this.type === 'B') {
this.klineB()
} else if (this.type === 'C') {
this.klineC()
} else if (this.type === 'D') {
this.klineD()
}
} else if ((mode = 'depth')) {
if (this.type === 'A') {
this.depthA()
} else if (this.type === 'B') {
this.depthB()
} else if (this.type === 'C') {
this.depthC()
} else if (this.type === 'D') {
this.depthD()
}
}
// 优化后:把条件判断拼接成字符串作为键,每个分支下的处理逻辑作为值,在使用时传入参数使用 Map 查询,这种方法非常适合于二元或多元条件判断。
let enums = new Map([
['kline_A', handleKlineA],
['kline_B', handleKlineB],
['kline_C', handleKlineC],
['kline_D', handleKlineD],
['kline_E', handleKlineE],
['depth_A', handleDepthA],
['depth_B', handleDepthB],
['depth_C', handleDepthC],
])
function action(mode, type){
let key = `${mode}_${type}`
let handleType = enums(key)
handleType()
}
3.6 使用数组新特性优化逻辑判断
使用 includes 处理多重条件
// 优化前
if( code === '202' || code === '203' || code === '204' ){
someMethod()
}
// 优化后
if( ['202','203','204'].includes(code) ){
someMethod()
}
3.7 策略模式优化分支逻辑
策略模式是一种对象行为型模式:定义一系列算法(对应各个业务逻辑),将每一个算法封装起来。每一个策略都会有一个标识名,根据策略选择要执行的函数,其实就是使用key寻找value,然后执行vlaue的过程。
//优化前
function percent50(price) {
// 五折的算法
}
function percent70(price) {
// 七折的算法
}
function percent90(price) {
// 九折的算法
}
function calculatePrice(price) {
if (五折的商品) {
return percent50(price);
}
if (七折的商品) {
return percent50(price);
}
if (九折的商品) {
return percent50(price);
}
}
calculatePrice(price);
//优化后
// 策略类
let strategy = {
// 5折
percent50(price) {
return price * 0.5;
},
// 7折
percent70(price) {
return price * 0.7;
},
// 9折
percent90(price) {
return price * 0.9;
},
// 满300-50
fullReduce50(price) {
// 执行满300-50的业务逻辑
},
// 满300-80
fullReduce80(price) {
// 执行满300-80的业务逻辑
},
// vip用户五折
vip50(price) {
// vip用户五折的业务逻辑
},
};
// 调用策略类中的方法
// 环境类
function calculatePrice(strategyName, price) {
return strategy[strategyName] && strategy[strategyName](price);
}
console.log(calculatePrice("percent50", 100)); // 50
3.8 封装内部函数
【解释】把 if-else 内的代码都封装成一个公共函数,函数的好处是屏蔽内部实现,缩短 if-else 分支的代码,代码结构和逻辑上清晰,能一下看出来每一个条件内做的功能。
// 优化前
toothAppointCancel(res: any) {
if (TYPE === XXXX || TYPE === YYYY) {
history.replace({
pathname: `/appointmentdetail?jdAppointmentId=${res.msg}&type=${TYPE}`,
});
} else {
window.location.reload();
}
}
//优化后
toothAppointCancel(res: any) {
const isTooth = () => {
return TYPE === XXXX || TYPE === YYYY;
};
const handleToothUpdate = () => {
history.replace({
pathname: `/appointmentdetail?jdAppointmentId=${res.msg}&type=${TYPE}`,
});
const handleReload = () => {
window.location.reload();
};
isTooth() ? handleToothUpdate() : handleReload();
}
3.9 用多态替换switch场景
拓展一下思路啦,当遇到有一个基础逻辑,基于这个基础逻辑又有一些变体,那么我们可以把基础逻辑放到超类,把各种变体逻辑放到各个子类
// 优化前
fixAppointToUrl: (obj: any, type: number) => {
let url = '';
switch (type) {
case TYPEA:
...
url = "B_JD_PE_FIX_URL";
break;
case TYPEB:
...
url = "BC_TOOTH_FIX_URL";
break;
case TYPEC:
...
url = "C_BACTERIN_FIX_URL";
break;
case TYPED:
...
url = "B_TOOTH_FIX_URL";
break;
default:
...
url = "DEFAULT_FIX_URL";
}
return url;
}
repeatAppointToUrl: (obj: any, type: number) => {
let url = '';
switch (type) {
case TYPEA:
...
url = "B_JD_PE_REPEAT_URL";
break;
case TYPEB:
...
url = "BC_TOOTH_REPEAT_URL";
break;
case TYPEC:
...
url = "C_BACTERIN_REPEAT_URL";
break;
case TYPED:
...
url = "B_TOOTH_REPEAT_URL";
break;
default:
...
url = "DEFAULT_REPEAT_URL";
}
return url;
}
// 优化后
class HandleUrl {
get fixUrl(){
return 'DEFAULT_FIX_URL';
}
get repeatUrl(){
return 'DEFAULT_REPEAT_URL';
}
}
class TYPEA extends HandleUrl{
get fixUrl(){
return 'B_JD_PE_FIX_URL';
}
get repeatUrl(){
return 'B_JD_PE_REPEAT_URL';
}
}
class TYPEB extends HandleUrl{
get fixUrl(){
return 'BC_TOOTH_FIX_URL';
}
get repeatUrl(){
return 'BC_TOOTH_REPEAT_URL';
}
}
class TYPEC extends HandleUrl{
get fixUrl(){
return 'C_BACTERIN_FIX_URL';
}
get repeatUrl(){
return 'C_BACTERIN_REPEAT_URL';
}
}
class TYPED extends HandleUrl{
get fixUrl(){
return 'B_TOOTH_FIX_URL';
}
get repeatUrl(){
return 'B_TOOTH_REPEAT_URL';
}
}
const typed = new TYPED();
console.log(typed.fixUrl)
四、总结
- 更少的嵌套,尽早
return。 - 倾向于使用对象或使用 map 结构来优化
if else,而不是Switch语句 。 - 多重判断时使用
Array.includes。 - 对 所有/部分 判断使用
Array.every&Array.some。 - 使用默认参数和解构 。
- 当一个项目中需要大量算法,大量匹配模式时可以考虑使用策略模式。
五、参考文献
[1]【前端词典】你或许可以这样优化 if else 结构
[3] if-else重构优化
[4] if-else逻辑判断优化