关于javascript:羡慕-C-的-switch-表达式不JS-也可以有

40次阅读

共计 2891 个字符,预计需要花费 8 分钟才能阅读完成。

对于 C/Java 语系的语言,都有 switch 语法。switch 语法用于多分支是一个规范的用法,但这个分支语法的各分支之间存在穿透性,所以须要 break 来切断逻辑,这也成为 switch 语法中最重要的一个替在缺点起源。此外,因为 switch 语句中各 case 的代码是在同一个作用域中,也会对代码造成一些不便。

C# 8.0 引入了 switch 表达式。C# 的 switch 表达式有着十分丰盛的语法元素,能够和模式匹配和解构等语法元素协同工作 —— 这些都不在这里细说,然而对传统的 switch 语句 进行了一些改良:

  1. 通过箭头 (=>) 标记解决了 case 和语句之间的一对一关系,不须要 break,不再穿透;
  2. 作为表达式,能够而且必须返回值;

也产生了新的束缚:

  1. switch 表达式肯定要详尽(逻辑肯定会走进某一个 case,能够通过弃元模式兜底),否则可能会在运行时引发异样。

在 C# 8.0 公布的同年,Java 12 也公布并引入了 switch 表达式预览。Java 的 switch 表达式实现比较简单,就是 switch 语句到 switch 表达式的间接转换,仅反对等值匹配。直到 2023 年 3 月 Java 20 公布,switch 表达式才开始反对模式匹配。相比之下,Kotlin 的 when 表达式走在了后面。

在这个问题上 JavaScript 仿佛走在了前面,不过在语言提供 switch 表达式之前,咱们能够尝试本人造个轮子。

思路当然是参考策略模式。假如有一个列表,这个列表里的每个元素都蕴含了两个因素:第一个用于判断是否命中,第二个是个函数,失去一个计算结果。而后写一个循环遍历列表的每个元素,一旦某个元素命中,就执行元素携带的函数取得后果,中断循环,返回后果。如果列表的最初一个元素必然命中,那么这个列表就是“详尽”的。

那么这个 when 函数可能会这样写(switch 是关键字,所以应用 when 来作为函数名):

// JS

function when(value, ...cases) {for (const { is, run} of cases) {if (is(value)) {return run(value);
        }
    }
    throw new Error("非详尽");
}

这里咱们假如每个状况 (case) 都含有 is 办法用于判断是否命中,用 run 办法保留命中后须要执行的操作。相应地,咱们能够经典的“拿分算等级”来进行测试:

// JS

function calcGrade(score) {
    return when(
        score,
        {is: v => v >= 0 && v < 80, run: v => ` 不合格 (${v})` },
        {is: v => v >= 80 && v < 100, run: v => ` 合格 (${v})` },
        {is: v => v == 100, run: v => ` 满分 (${v})` },
        {is: _ => true, run: v => ` 有效 (${v})` },
    );
}

for (let i = 0; i < 50; i++) {const v = 70 + ~~(Math.random() * 35);
    console.log(calcGrade(v));
}

calcGrade 实现中 when 的 case 列表最初一项采纳了“永真”断言,所以走到这一项的时候肯定会命中,从逻辑上来永远不会触发 Error。如果是非“详尽”的状况列表,就有可能触发 Error。

不过当初从测试代码中就发现了两个问题:

  1. is 断言是采纳函数的模式,不能简略地间接按值匹配;
  2. 从调用模式上来说,score 和前面的 case 元素是同级的,模式上辨别不显著;
  3. 每次都要写 isrun,条件多了写起来也烦。

持续改良 ——

// JS

function when(value) {
    // when 的参数先给 switch 的值
    // 返回一个函数来解决分支匹配 ②
    return function (...cases) {for (const [is, run] of cases) {
//                 ^^^^^^^^^ 从对象改为元组(数组)③
            if (value === is || (typeof is == "function" && is(value))) {
//                    ^^^^^^ 准确判断 ①
//                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 断言函数判断
                return  run(value);
//                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 可指定行为(函数)}
        }
        throw new Error("非详尽");
    };
}

function calcGrade(score) {return when(score)(
//         ^^^^^^^^^^^ 这里返回的是匹配解决的函数
        [v => v >= 0 && v < 80, v => ` 不合格 (${v})`],
        [v => v >= 80 && v < 100, v => ` 合格 (${v})`],
        [100, () => "满分 (100)"],
//       ^^^ 能够指定匹配的值
//            ^^ 计算不须要参数,能够不申明
        [_ => true, v => ` 有效 (${v})`],
//       ^^^^^^^^^ 兜底的永真断言
    );
}

为什么兜底断言必须应用一个函数呢?因为 true 值也有可能是对应一种料想的分支状况。因为这个 when 是通过语义来实现而不是通过语法来实现的,所以这里没方法定义一个平安的兜底断言语法,只有用断言函数会 绝对 平安。

至此为止咱们曾经根本实现了 switch 表达式 (when),把它升级成 TypeScript

// TypeScript

type CaseCondition<T> = T extends Function ? never : ((t: T) => boolean) | T
type Case<T, R> = [CaseCondition<T>, (t: T) => R];

function when<T>(value: T): <R>(...cases: Case<T, R>[]) => R {return function<R>(...cases: Case<T, R>[]): R {for (const [is, run] of cases) {if (value === is || (typeof is == "function" && is(value))) {return run(value);
            }
        }
        throw new Error("非详尽");
    };
}

function calcGrade(score: number) {return when(score)([v => v >= 0 && v < 80, v => ` 不合格 (${v})`],
        [v => v >= 80 && v < 100, v => ` 合格 (${v})`],
        [100, () => "满分 (100)"],
        [_ => true, v => ` 有效 (${v})`],
    );
}

这段代码当然能够间接用,然而如果应用 npm 可能会更不便一点:

npm install @jamesfancy/when
// TypeScript

import {when} from "@jamesfancy/when";

function calcGrade(score: number) {return when(score)([v => v >= 0 && v < 80, v => ` 不合格 (${v})`],
        [v => v >= 80 && v < 100, v => ` 合格 (${v})`],
        [100, () => "满分 (100)"],
        [_ => true, v => ` 有效 (${v})`],
    );
}

正文完
 0