mhahaha / js-snippets

Daily JS Snippets
0 stars 0 forks source link

也许你可以更少更好地使用if-else分支条件 #4

Open mhahaha opened 5 years ago

mhahaha commented 5 years ago

在前端JS编码过程,避免不了写 if-else 或 switch-case 分支语句,代码风格因人而异,但其实我们有很多种方式来减少一些晦涩的条件判断,减少嵌套。下面我们从前端JS层面来探讨下:怎样更少更好地写分支条件?

1. 单一条件用三目运算

if (isTrue) {
    return 1;
}
return 2;

三目运算改写:

return isTrue ? 1 : 2;

2. 善用逻辑运算符 &&(and) 和 ||(or)

if (isTrue) {
    setTrueValue();
}

if (!isFalse) {
    setTrueValue();
}

运用逻辑运算符改写:

isTrue && setTrueValue();

isFalse || setTrueValue();

注:尽量避免运用三目运算或逻辑运算符写较复杂逻辑,保证良好的可读性!

3. 多条件判断使用Array.includes()

function sayWords (role) {
    if (role === 'javascript' || role === 'go' || role === 'python' || role === 'java') {
        console.log('Hello World ~');
    }
}

这个写法逻辑没什么问题,相信大家也比较常见,但这种条件一多就特别冗长难看。

Array.includes() 改进写法:

function sayWords (role) {
    const ROLES_SAY_HELLO = ['javascript', 'go', 'python', 'java'];

    if (ROLES_SAY_HELLO.includes(role)) {
        console.log('Hello World ~');
    }
}

显然,我们将所有符合条件的角色提取到数组中进行维护,实现逻辑看起来更加简洁,后面即便增加条件,我们只需丰富数组就好了。

另外一种多条件情况可能存在多个维度,不仅仅像上面的单一值的判断,会涉及逻辑判断等,例如:

if (sex === 'man' && age < 30 && hasRequiredProperty(property) && acountNumber.indexOf('666666') !== -1) {
    doSomething();
}

类似上面这种情况,条件增加直接在if后追加条件会降低代码可读性,这时比较好的一个做法是将条件一一提取到变量里面:


let isMan = sex === 'man',
    isAgeAgree = age < 30,
    hasRequiredProperty = hasRequiredProperty(property),
    isAcountAgree = acountNumber.indexOf('666666') !== -1;

// 注意特殊条件补全注释
let isAvailable = isMan && isAgeAgree && hasRequiredProperty && isAcountAgree;

isAvailable && doSomething();

优点:代码可读性高

4. 尽早返回,减少嵌套

function sayWords (role) {
    if (typeof role === 'string') {
        const ROLES_SAY_HELLO = ['javascript', 'go', 'python', 'java'];

        if (ROLES_SAY_HELLO.includes(role)) {
            console.log('Hello World ~');
        }
    } else {
        throw new Error('role type error!');
    }
}

写法上很符合我们常规思维方式,但从编码角度来看这段代码,我们做如下改进:

function sayWords (role) {

    // 非法类型抛出异常
    if (typeof role !== 'string') {
        throw new Error('role type error!');
    }

    const ROLES_SAY_HELLO = ['javascript', 'go', 'python', 'java'];

    if (ROLES_SAY_HELLO.includes(role)) {
        console.log('Hello World ~');
    }
}

这种方式第一步就把异常类型的错误抛出,也相当于我们常规的 return ,那么我们就减少了主逻辑的一层嵌套了。特别是主逻辑比较长的时候,这种方式的优势就特别明显,只需始终遵循 无效条件提早返回 这个规则。

5. 善用JS函数的默认形参

function sayWords (role) {

    // 非法类型抛出异常
    if (typeof role !== 'string') {
        throw new Error('role type error!');
    }

    if (!role) {
        role = 'javascript';
    }

    // 再或者:role = role || 'javascript'

    const ROLES_SAY_HELLO = ['javascript', 'go', 'python', 'java'];

    if (ROLES_SAY_HELLO.includes(role)) {
        console.log('Hello World ~');
    }
}

我们也不少在函数里面添加类似上面role默认值的逻辑:

if (!role) {
    role = 'javascript';
}

role = role || 'javascript'

改用函数默认形参后,我们可以省去这块逻辑。

function sayWords (role = 'javascript') {

    // 非法类型抛出异常
    if (typeof role !== 'string') {
        throw new Error('role type error!');
    }

    // 再或者:role = role || 'javascript'

    const ROLES_SAY_HELLO = ['javascript', 'go', 'python', 'java'];

    if (ROLES_SAY_HELLO.includes(role)) {
        console.log('Hello World ~');
    }
}

默认形参的优点在于我们可以很直观地知道参数的类型和默认设置,不用担心undefined这类无效类型对逻辑的干扰。

6. 使用Object HashMap 代替 if-else / switch-case

function sayWords (role = 'javascript') {

    if (role === 'javascript') {
        return ['vue', 'react'];
    }

    if (role === 'python') {
        return ['Django'];
    }

    if (role === 'java') {
        return ['JavaWeb', 'Spring'];
    }

    return [];
}

function sayWords (role = 'javascript') {

    switch (role) {
        case 'javascript':
            return ['vue', 'react'];

        case 'python':
            return ['Django'];

        case 'java':
            return ['JavaWeb', 'Spring'];

        default:
            return [];

    }
}

类似上面这类写法,我们完全可以不用if-else进行判断,一个字面量对象完全可以搞定。

function sayWords (role = 'javascript') {

    const LANG_STACK = {
        javascript: ['vue', 'react', 'angular'],
        python: ['Django'],
        java: ['JavaWeb', 'Spring']
    }

    return LANG_STACK[role] || [];
}

利用Object维护所有分支信息后,我们省去了晦涩的if-else,逻辑也比较集中。当然我们还会遇到另外一种稍微复杂的情况,例如:

function sayWords (role = 'javascript', name = 'css') {

    if (role === 'javascript') {

        /**
         * 这里可能是多重处理逻辑
         */
        let availableNames = getNamesByRole(role);

        if (availableNames.includes(name)) {
            return ['vue', 'react'];
        }
        return [];
    }

    if (role === 'python') {

        /**
         * 这里可能是多重处理逻辑
         */
        let isAvailable = checkRoleName(role, name);

        if (isAvailable) {
            return ['Django'];
        } 
        return [name];
    }

    if (role === 'java') {

        /**
         * 这里可能是多重处理逻辑
         */
        let availableNames = getNamesByRole(role);

        if (availableNames.includes(name)) {
            return ['JavaWeb', 'Spring'];
        }
        return [role];
    }
}

例如上面写法,当逻辑分支增加,整个函数就显得很大很重,嵌套层级也越来越多,这个时候我们要想着怎么去优化这块的逻辑。 首先,场景合适的话我们依旧可以选择使用Object字面量对象进行逻辑的收敛,不同点在于我们将每一小块的逻辑提取出来作为一个函数单独维护。

改进示例:

function sayWords (role = 'javascript', name = 'css') {

    const LANG_STACK = {
        javascript: jsHandle(role, name),
        python: pythonHandle(role, name),
        java: javaHandle(role, name)
    }

    return LANG_STACK[role];
}

/**
 * javascript 语言处理器
 * @method jsHandle
 * @param {String} role
 * @param {String} name
 * 
 * @returns {Array}
 */
function jsHandle (role, name) {
    let availableNames = getNamesByRole(role);

    if (availableNames.includes(name)) {
        return ['vue', 'react'];
    }
    return [];
}

/**
 * python 语言处理器
 * @method pythonHandle
 * @param {String} role
 * @param {String} name
 * 
 * @returns {Array}
 */
function pythonHandle (role, name) {
    let isAvailable = checkRoleName(role, name);

    if (isAvailable) {
        return ['Django'];
    } 
    return [name];
}

/**
 * java 语言处理器
 * @method javaHandle
 * @param {String} role
 * @param {String} name
 * 
 * @returns {Array}
 */
function javaHandle (role, name) {
    let availableNames = getNamesByRole(role);

    if (availableNames.includes(name)) {
        return ['JavaWeb', 'Spring'];
    }
    return [role];
}

显然,主逻辑拆分之后,很简洁。我们运用策略模式对每一种情况做相应的差分封装处理,提取出最小单元,后面即便再增加更多情况,我们只需增加一个处理函数,然后在主逻辑配置对象信息就行,无需追加if-else。

总结

最近看了老项目的部分代码,看到很多不同的条件分支处理方法,个人对分支这块比较敏感,就临时梳理纪录下。当然,多人协作过程也难免,每个人不同的处理方法,某种意义上来讲没有对错之分,只是有些情况下可以更好。

方法不仅仅上面这些,这些只是我们平常开发编码过程可能比较常见的几种写法及改进方式,具体的还是需要我们在实际场景里面多思考,灵活应变,希望大家编码过程带几分“思考”,带几分“强迫症”,不断寻找最优写法,写出更nice的代码。

最后,不对之处欢迎指正!