【JavaScript 基础】-- 不做 if-else 怪

前瞻

我们日常开始发时经常遇到复杂逻辑判断的情况,通常大家可以用 if-else 来实现多个条件判断。但随着逻辑复杂度的增加,代码中的 if-else 会变得越来越臃肿。那么如何更优雅的写判断逻辑,拒绝做一个 if-else 怪呢?以下是我的一些优化思想


举例

有个需求是这样的:马上中秋节要来了,我们本次商场要做大促销活动。对每个商品,我通过在给它设置不同的价格类型,让它展示不同的价格。产品订的逻辑是这样:

  • 当价格类型为“预售价”时,打 9.5 折
  • 当价格类型为“大促价”时,打 9 折
  • 当价格类型为“返场价”时,打 8 折
  • 当价格类型为“新人价”时,打 8 折
  • 当价格类型为“尝鲜价”时,打 5 折
  • 当价格类型为“正常价”时,原价

作为一名资深 if-else 怪,我们这样写:

/**
 * @description 获取价格
 * @param {*} tag 商品类型: pre 预售价 onSale 大促销 back 返场价 fresh 尝鲜价 normal 正常价 new 正常价
 * @param {*} price 原价
 * @return {*}
 */
function askPrice(tag, price) {
    if (tag === 'pre') {
        return price * 0.95
    } else if (tag === 'onSale') {
        return price * 0.9
    } else if (tag === 'back') {
        return price * 0.8
    } else if (tag === 'new') {
        return price * 0.8
    } else if (tag === 'fresh') {
        return price * 0.5
    } else if (tag === 'normal') {
        return price
    }
}

除了 if-else 之外,大家也可以很轻易的提出这段代码的改写方案,switch:

function askPrice(tag, price) {
    switch (tag) {
        case 'pre':
            return price * 0.95
        case 'onSale':
            return price * 0.9
        case 'back':
        case 'new':
            return price * 0.8
        case 'fresh':
            return price * 0.5
        case 'normal':
            return price
    }
}

这样看起来比 if-else 清晰多了,同时也将返场价格和新人价整合到了一起。


思考

上面的代码乍一看好像没什么毛病,但是仔细一看其实还是有一些优化的空间。

第一点:你有没有发现这个小小的函数里塞了6个逻辑。这会导致什么后果呢?如果一处逻辑错误,将会导致整个函数无法使用。这时候开发人员就需要一个个逻辑去查看。

第二点:如果产品又新增了一种 XX价,那是不是意味着又要加一重 if-else

第三点:尽管 if-else/swicth 就能完成效果,但是如果逻辑更加复杂不仅仅是打折还涉及到满减时。还继续使用 if-else 就会导致代码很冗余

针对以上几点最终可能会导致的后果就是,如果有BUG出现。开发人员要核对所有逻辑,测试人员也要所有逻辑都重新测。作为一个有追求的程序员,这样肯定是不行的。


解决方法

针对第一点,我们可以所有的逻辑都抽离出来:

function prePrice(price) {
    // do sth
    return price * 0.95
}

function onSalePcice(price) {
    // do sth
    return price * 0.9
}

function backPrice(price) {
    // do sth
    return price * 0.8
}

function newPrice(price) {
    // do sth
    return price * 0.8
}

function freshPrice(price) {
    // do sth
    return price * 0.5
}

function normalPrice(price) {
    // do sth
    return price
}


function askPrice(tag, price) {
    if (tag === 'pre') {
        return prePrice(price)
    } else if (tag === 'onSale') {
        return onSalePcice(price)
    } else if (tag === 'back') {
        return backPrice(price)
    } else if (tag === 'new') {
        return newPrice(price)
    } else if (tag === 'fresh') {
        return freshPrice(price)
    } else if (tag === 'normal') {
        return normalPrice(price)
    }
}

改成这样之后,至少我们在遇到问题时就能快速定位到某个具体的函数。

写完之后,看代码我们可以发现。虽然将每个模块都抽离成一个方法,但是如果我们新增一个模块时,还是需要在 askPrice 函数里加 if-else。 那么有没有什么更好的方式改写 askPrice 函数,让我们在新增模块时,不用改 askPice 函数呢?

这时聪明的同学就会想到 对象映射

const priceMap = {
    pre(price) {
        // do sth
        return price * 0.95;
    },
    onSale(price) {
        // do sth
        return price * 0.9;
    },
    back(price) {
        // do sth
        return price * 0.8;
    },
    new(price) {
        // do sth
        return price * 0.8;
    },
    fresh(price) {
        // do sth
        return price * 0.5;
    },
    normal(price) {
        // do sth
        return price;
    },
};

function askPrice(tag, price) {
    return priceMap[tag](price)
}

是不是这样之后,我们增加新模块,就不需要去动 askPrice 函数,只需要去更改 priceMap 就可以了。同时还去除了 if-else。 整个代码看起来就更加的清爽优雅!


问题升级

原先的打折,力度还不够大。现在要加上满减,并且还增加一个VIP的角色,VIP 的优惠力度和普通用户不一样。

原谅我不仔细写每种类型的优惠情况,因为太多了。我们的目的也不是为了关注这个。

如果使用 if-else 的话代码是这样的:

/**
 * @description 获取价格
 * @param {*} tag 商品类型: pre 预售价 onSale 大促销 back 返场价 fresh 尝鲜价 normal 正常价 new 正常价
 * @param {*} identity 身份: vip 高级用户 guest 普通用户
 * @param {*} price 价格
 */
function askPrice(tag, identity, price) {
    if (identity === 'guest') {
        if (tag === 'pre') {
            // do sth
        } else if (tag === 'onSale') {
            // do sth
        } else if (tag === 'back') {
            // do sth
        } else if (tag === 'new') {
            // do sth
        } else if (tag === 'fresh') {
            // do sth
        } else if (tag === 'normal') {
            // do sth
        }
    } else if (identity === 'vip') {
        if (tag === 'pre') {
            // do sth
        } else if (tag === 'onSale') {
            // do sth
        } else if (tag === 'back') {
            // do sth
        } else if (tag === 'new') {
            // do sth
        } else if (tag === 'fresh') {
            // do sth
        } else if (tag === 'normal') {
            // do sth
        }
    }
}

从上面的例子我们可以看到,当你的逻辑升级时,你的判断量会加倍,你的代码量也会加倍。所有我们还是需要使用到对象映射,来实现 askPrice 方法。

const priceMap = {
    'vip_pre': () => {/* fn1 */ },
    'vip_onSale': () => {/* fn2 */ },
    'vip_back': () => {/* fn3*/ },
    'vip_new': () => {/* fn4*/ },
    'vip_fresh': () => {/* fn5 */ },
    'vip_normal': () => {/* fn6 */ },
    'guest_pre': () => {/* fn7 */ },
    'guest_onSale': () => {/* fn8 */ },
    'guest_back': () => {/* fn9 */ },
    'guest_new': () => {/* fn10 */ },
    'guest_fresh': () => {/* fn11 */ },
    'guest_normal': () => {/* fn12 */ },
}

function askPrice(tag, identity, price) {
    const onPrice = priceMap[`${identity}_${tag}`]
    onPrice.call(this, price)
}

从上面的例子可以看出来,将有多个条件拼接成字符串,并通过以条件拼接字符串作为键,以处理函数作为值的对象进行查找并执行,这种写法在多元条件判断时候尤其好用。


需求总是多变的,这时候产品又改需求了,guest 用户的某几种类型,优惠力度改成一样。于是勤劳的程序员又开始改代码了。

写出来大概是这样:

const priceMap = {
    'guest_pre': () => {/* fn1 */ },
    'guest_onSale': () => {/* fn1 */ },
    'guest_back': () => {/* fn1 */ },
    'guest_new': () => {/* fn1 */ },
    'guest_fresh': () => {/* fn11 */ },
    'guest_normal': () => {/* fn12 */ },
    // ...
}

这样写已经能满足日常需求了,但认真一点讲,上面重写了4次 fn1 还是有点不过优雅。那么有什么办法可以把这4个合成一个呢?也就是将这4个键合成一个呢?

聪明的我们这时候肯定能想到,既然要让键变化并且又是键值对的形式。那么 Map 对象就再合适不过了。

整理之后代码是这样:

const priceMap = () => {
    const fn1 = () => {/*do sth*/ }
    //...
    return new Map([
        [/^guest_(pre||onSale||back||new)$/, fn1],
        [/^guest_fresh$/, fn2],
        //...
    ])
}

function askPrice(tag, identity, price) {
    let onPrice = [...priceMap()].filter(([key, value]) => key.test(`${identity}_${tag}`))
    onPrice.forEach(([key, value]) => value.call(this, price))
}

充分 Map 的优点,使用正则作为 key。这里代码看起来是不是更加的高级,同时也能很好的应对更加复杂的判断。


小小的东西其实也能蕴含很多学问。拒绝成为 if-else 怪,从现在开始!加油~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值