• JavaScript 的 switch 有四样写法,你知道么?


    JavaScript 的 switch 有四样写法,你知道么?

    说到对分支的处理,写法可就多了去了。if 分支写法可以算一种,switch 分支写法可以算第二种,第三种是使用策略模式,如果要把条件运算符也算上的话,嗯,刚好四种。

    不过本文的主角是 switch。大家都了解 switch 的写法一般来说是 switch 变量或表达式,case 常量。嗯,比如说,一个百分制成绩,90 及 90 分以上算优秀,80 及以上 90 以下算良好,60 及以上 80 以下算合格,60 以下为不合格,用 switch 大概会这么写:

    function calcGrade(score) {
        const line = score / 10 | 0;
        switch (line) {
            case 10: case 9:
                return "优秀";
            case 8:
                return "良好";
            case 7: case 6:
                return "合格";
            default:
                return "不合格";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码中 score / 10 | 0Math.floor(score / 10) 是一样的效果,就是除以 10 取商的整数部分。

    这段 switch 用得中规中矩,用取整的办法来避免使用一长串 if … else 分支也算是取了巧。

    但是现在规则改了,将合格和良好的分隔点从 80 分降到 75 分,该怎么办?

    按上面取整的办法依然可以,不过这次除数不再是 10,而是 5。相应地,case 也多了很多:

    • 18、19、20 是优秀
    • 15、16、17 是良好
    • 12、13、14 是合格
    • 剩下的是不合格

    写 9 个 case,真不如用 if … else 算了。

    是吗?其实用 switch 也有简单一些的写法:

    function calcGrade(score) {
        switch (true) {
            case score >= 90:
                return "优秀";
            case score >= 75:
                return "良好";
            case score >= 60:
                return "合格";
            default:
                return "不合格";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    是不是感觉有些奇怪?这完全不是习惯了的 switch 表达式 case 常量,而是正好相反,switch 常量 case 表达式!如果你拿这段程序去跑一下,会发现一点问题都没有。因为——switchcase 是按 === 来匹配的,它并不在乎是表达式还是常量,或者说,switch 和 case 后面都可以接表达式!

    JavaScript 里一切都可以是表达式 …… 如果不是,用 IIFE 封装一个就是了

    function calcGrade(score) {
        return (value => {
            switch (true) {
                case value >= 90:
                    return "优秀";
                case value >= 75:
                    return "良好";
                case value >= 60:
                    return "合格";
                default:
                    return "不合格";
            }
        })(score);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意这里把 score 作为 IIFE 的参数,是因为在实际使用中,可能需要传入的是一个表达式。这种情况下应该提前求值,而且只求一次(避免替在的副作用)。

    不过这样的封装显然没什么意义,如果真要这样封装,不如封成策略:

    function calcGrade(score) {
        return ((value, rules) => rules.find(({ t }) => t(value)).v)(
            score,
            [
                { t: n => n >= 90, v: "优秀" },
                { t: n => n >= 75, v: "良好" },
                { t: n => n >= 60, v: "合格" },
                { t: () => true, v: "不合格" },
            ]
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    每项策略都是一个含有 tester (t) 和值 (v) 的对象。tester 是一个判断函数,传入需要判断的值,也就是 switch (表达式) 这里表达式,而这个表达式也是提前求值之后作为 IIFE 的参数传入的。应用策略的过程简单粗暴,就是找到第一个符合条件的策略,把它的值取出来。

    当然这样用策略有点大材小用。真正需要用策略的情况,策略中通常不是一个值,而是一个行为,也就函数。

    我们知道在 switch 语句中,各个 case 之间是在同一个作用域内,所以不能在两个 case 语句中声明同一个局部变量。虽然用 { } 包裹可以解决这些问题,但代码看起来不怎么好看,特别是还要注意不要忘了 break。如果用策略的话,看起来可能会顺眼一眼,也不用担心 break 的问题:

    这里为了演示,在策略行为中将先输出成绩,再返回等级。

    function calcGrade(score) {
        return ((value, rules) => rules.find(({ t }) => t(value)).fn(value))(
            score,
            [
                {
                    t: n => n >= 90,
                    fn: score => {
                        const grade = "优秀";
                        console.log(grade, score);
                        return grade;
                    }
                },
                {
                    t: n => n >= 75,
                    fn: score => {
                        const grade = "良好";
                        console.log(grade, score);
                        return grade;
                    }
                },
                {
                    t: n => n >= 60,
                    fn: score => {
                        const grade = "合格";
                        console.log(grade, score);
                        return grade;
                    }
                },
                {
                    t: () => true,
                    fn: score => {
                        const grade = "不合格";
                        console.log(grade, score);
                        return grade;
                    }
                },
            ]
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    代码确实有点长,因为有策略行为逻辑在里面。如果真的是要当作 switch 表达式来用的话,策略部分应该是一个表达式,不会太长的。上面的代码中,策略行为相似,可以封装成一个函数,这样就能写成表达式的形式了:

    function calcGrade(score) {
        const printGrade = (grade, score) => {
            console.log(grade, score);
            return grade;
        };
    
        return ((value, rules) => rules.find(({ t }) => t(value)).fn(value))(
            score,
            [
                { t: n => n >= 90, fn: score => printGrade("优秀", score) },
                { t: n => n >= 75, fn: score => printGrade("良好", score) },
                { t: n => n >= 60, fn: score => printGrade("合格", score) },
                { t: () => true, fn: score => printGrade("不合格", score) },
            ]
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    Windows 常用工具记录
    极简7照训练法,奇趣相机引领儿童AI摄影潮流
    【硕士论文】《动态场景下的语义三维地图构建 》西南科技大学 余东应
    云安全-云原生k8s攻击点(8080,6443,10250未授权攻击点)
    【算法专题--链表】两两交换链表中的节点 -- 高频面试题(图文详解,小白一看就懂!!!)
    什么是脏读、不可重复读、幻读讲解
    Python连本地MySQL接数据库
    C#添加WebApi,配置Swagger
    电视剧中的程序员,是真的敲代码吗?
    C++使用openssl对AES-256-ECB PKCS7 加解密
  • 原文地址:https://blog.csdn.net/agonie201218/article/details/125602044