0

switch 有四样写法你知道么

 1 year ago
source link: https://blog.51cto.com/jamesfancy/5434579
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

switch 有四样写法你知道么

原创

边城狂人 2022-07-01 16:26:06 博主文章分类:Web开发 ©著作权

文章标签 javascript 策略模式 分支 文章分类 JavaScript 前端开发 阅读数174

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 "不合格";
}
}

代码中 ​​score / 10 | 0​​ 和 ​​Math.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 "不合格";
}
}

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

是的,表达式!

所以上面那个示例中,把 ​​switch(true)​​​ 改成 ​​switch( 2 > 1)​​ 也是一样的效果。

好啦,脑洞已开。switch 到底有多少种写法已经不重要了。接下来要研究的是 switch 的变种 ——

看到 C# 有 switch 表达式,眼馋,能实现吗?

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

function calcGrade(score) {
return (value => {
switch (true) {
case value >= 90:
return "优秀";
case value >= 75:
return "良好";
case value >= 60:
return "合格";
default:
return "不合格";
}
})(score);
}

注意这里把 ​​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: "不合格" },
]
);
}

每项策略都是一个含有 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;
}
},
]
);
}

代码确实有点长,因为有策略行为逻辑在里面。如果真的是要当作 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) },
]
);
}

现在看起来是不是像样了?

上面的代码形式各异,干的事情都差不多,也没有谁优谁劣的比较。看得顺眼怎么都优雅,看不顺眼怎么都不受宠。在不同的情况下,选用合适的做法就好。上面的代码使用的 ​​find()​​​ 来查找策略,如果改用 ​​filter()​​​,那又会是另一番景象了 ​​~(~ ̄▽ ̄)~​​。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK