网站首页 > 技术文章 正文
对 JS 开关说“不”!
许多开发人员都喜欢讨厌 switch 语句,包括我在内。这不是因为该语句不起作用,也不是因为它太难了。
理解 switch 语句是如何工作的很容易,问题是当你在现实世界中遇到它时,你必须停止你正在做的一切,专注于阅读它以确保你没有遗漏任何东西,比如缺少的 break 语句可能会导致一些不需要的行为或在 switch 的单个 case 中出现大约 20 行代码。
重点是,让我在这里使用一个花哨的术语:理解 switch 语句(在现实世界中)所需的认知负担非常重。而且我相信,作为开发人员,我们的目标是编写易于人类阅读的代码。在这方面,这个特定的声明没有帮助。
但是,与其抨击它并向您展示其他所有人都向您展示的关于如何避免它的三个相同示例(包括我刚才),让我们来看看一种称为:多方法的函数式编程技术。
什么是多方法?
我在采访 Yehonathan Sharvit 的播客“20 MinJS”时遇到了这个学期,内容是关于他即将出版的 Manning 书籍“面向数据的编程”。
他提出这个概念作为继承的功能替代品,它绝对适用。在这个过程中,他还展示了如何用它来替换 switch 语句。所以让我们暂时忽略 OOP,只关注第二部分:摆脱代码中丑陋的开关。
那么什么是多方法呢?它只是一个能够根据接收到的参数选择最佳实现的函数。换句话说,想象一下如果你把丑陋的 switch 语句放在一个函数中,然后对所有人隐藏实现。
唯一不同的是,您的解决方案仅适用于单个函数,今天我们将讨论如何动态生成多个多方法。
多方法是什么样的?
当然,每种语言都会有其变化,但我今天将专注于 JavaScript。
在这种语言中,将使用多种方法,如下所示:
//the data we'll use
const myDog = {
type: "dog",
name:"Robert"
}
const myCat = {
type: "cat",
name: "Steffan"
}
//the custom implementations
function greetDogs (dog) {
console.log("Hello dear Dog, how are you today", dog.name, "?")
}
function greetCats(cat) {
console.log("What's up", cat.name, "?")
}
//defining our multi-method
let greeter = null
greeter = multi(
animal => animal.type,
method("dog", greetDogs),
method("cat", greetCats)
)(greeter)
// calling our multi-method
greeter(myDog)
greeter(myCat)
这个例子中发生了很多事情,让我解释一下:
- 我定义了 2 个对象, myCat 和 myDog ,它们将是我用作参数的对象,也是多方法需要用来决定如何表现的对象。
- 我有 2 个自定义函数,greetDogs 和 greetCats 我有 2 个自定义函数,greetDogs 和 greetCats,每个函数的实现都略有不同。这些将代表每个 switch 的 case 语句中的代码。
- 然后我调用了一些函数,特别是 multi 和 method 来在 greeter 中定义我的多方法。 multi 函数接收 3 个属性:一个调度程序,它本质上返回我们将用来识别要执行的正确逻辑的值和两个方法,每个方法代表一个 switch 的 case 语句。请注意每次对方法的调用如何首先指定将触发第二个参数的值(这是实际逻辑所在的位置)。
- 最后,我使用相同的函数(我的多方法)来执行 2 个不同的逻辑,而无需在任何地方编写单个 switch 或 IF 语句。
多方法有什么好处?
当然,我们在这里并没有做任何魔术,我们只是重写了表达决策逻辑的方式,由 switch 语句显示,如下所示:
switch(animal.type) {
case "dog":
greetDogs(animal);
break;
case "cat":
greetCats(animal);
break;
}
那么,当我们可以做到这一点时,为什么还要克服使用多方法的麻烦呢?重点是可读性。
switch 语句在显示我们的决策逻辑的实现方面非常开放。换句话说,这个声明是非常必要的。它向您展示了我们的决策树的内部运作方式,这意味着阅读它的人将不得不在心理上解析代码。因此,我们回到认知负荷的概念。我们让你,开发者,阅读和心理解析代码。
请注意,大多数开发人员在遇到上述开关时不会眨眼。但当然,这也不是一个现实的例子。通常情况下的语句包含更多的代码并且更难阅读。
然而,多方法方法隐藏了决策逻辑的内部,你所知道的只是你正在设置它,它会以某种方式工作。您更关心功能而不是实际实现。这被称为“声明式编程”,它有助于提高代码的可读性,同时降低开发人员的认知负担。那是因为它为逻辑增加了一层抽象,从而为我们提供了更接近人类语言表达自己的工具。
如果您还不相信,还有另一个优势:可扩展性。
如果您需要向 switch 添加另一个选项,则必须返回代码并修改相同的 switch ,例如,如果您碰巧忘记添加 break 语句,则可能会导致问题。像这样:
switch(animal.type) {
case "rabbit":
greetRabbits(animal);
case "dog":
greetDogs(animal);
break;
case "cat":
greetCats(animal);
break;
}
再一次,愚蠢的例子,但有一个更长的,真实的例子,这样做的机会增加了。
如果你不熟悉这种行为,当动物类型是“兔子”时,第一种情况下缺少的中断将导致它也执行第二种情况的逻辑。
然而,使用多种方法,我们会不断地将它扩展到我们想要的任何地方:
let extendedGreeter = multi(
animal => animal.type,
method("parrot", sayHiParrot)
)(greeter)
现在这个新的extendedGreeter 将适用于“dog”、“cat”和“parrot”,我们不必回头修改已经存在的代码。
这是一个巨大的好处,因为我们都知道每次我们接触工作代码时,我们都会有很小的机会引入一个错误。 在这里,我们将这个机会减少到 0。
实现多方法库
首先,只要知道已经有图书馆可以解决这个问题。
也就是说,对这些实现进行逆向工程总是很有趣,所以让我们看看我们将如何实现一个基本的多方法库以符合目前显示的示例。
弄清楚这一点的关键是要了解我们需要一个调度程序函数来为我们提供实际值,我们将使用该值作为了解要执行哪个方法的关键。 而且我们不能真正对 switch 语句进行硬编码,因为选项的数量不是固定的。
话虽如此,我给你实现:
function method(value, fn) {
return {value, fn}
}
function multi(dispatcher, ...methods) {
return (originalFn) => {
return (elem) => {
let key = dispatcher(elem)
let method = methods.find( m => m.value === key)
if(!method) {
if(originalFn) {
return originalFn(elem)
} else {
throw new Error("No sure what to do with this option!")
}
}
return method.fn(elem)
}
}
}
方法函数只是将键与实际逻辑耦合,仅此而已。有趣的代码在 multi 函数内部,它返回一个匿名函数,采用原始函数并返回一个新函数,该函数可以根据调度程序代码返回的值(我们的第一个参数)执行不同的操作。
让我们一步一步运行它:
- 首先,第 8 行中的函数调用了一个属性(比如 myDog )。
- 在第 9 行,我们的调度程序逻辑获取 myDog 并返回其类型,即“dog”。
- 然后在第 10 行,我们找到第一个匹配类型的方法。
- 如果没有方法,但我们有一个有效的“originalFn”(意思是,我们正在扩展一个原始的多方法),我们会让它处理这种情况。否则,我们将抛出异常,因为我们对此无能为力。
- 然而,如果找到了一个方法,我们在第 18 行执行它并将原始属性“myDog”传递给它。
就是这样。没那么复杂吧?当然,如果你想允许“默认”情况而不是抛出异常,或者如果你想处理多属性决策(比如根据类型决定逻辑和名称属性,而不仅仅是第一个),这是一个开关不会让你顺便说一句的东西。
但同样,如果您打算使用多方法,建议您使用现有库之一,而不是尝试自己实现它。
多方法是摆脱没人想要的讨厌开关的一种有趣且有趣的方式。这也是提高代码可读性的好机会。因此,请确保在决定坚持使用开关之前先尝试一下,因为您已经知道它们是如何工作的。
您过去尝试过多种方法吗?你对他们有什么看法?在下面发表评论,让我们聊天!
构建可组合的 Web 应用程序
不要构建 Web 巨石。使用 Bit 在您喜欢的框架(如 React 或 Node)中创建和组合解耦的软件组件。构建具有强大且令人愉悦的开发体验的可扩展和模块化应用程序。
将您的团队带到 Bit Cloud,共同托管和协作组件,并作为一个团队加速、扩展和标准化开发。尝试使用设计系统或微前端的可组合前端,或使用服务器端组件探索可组合后端。
猜你喜欢
- 2024-11-27 VirtualBox 7.1.2 发布! 带来多项 GUI 更新,无人值守安装已彻底删除
- 2024-11-27 Spring boot+Mybatisplus用AR模式实现逻辑删除操作
- 2024-11-27 碎片时间学编程「127]:从数组中删除元素
- 2024-11-27 JavaScript程序员需要掌握的5个debug技巧
- 2024-11-27 python 列表删除
- 2024-11-27 souce-map-js + Vue 还原生成环境报错,让JS报错无所遁形
- 2024-11-27 如何使用 rmdir 命令删除目录?
- 2024-11-27 illustrator插件-常用功能开发-删除所有蒙版-js脚本开发-AI插件
- 2024-11-27 如何在 Ubuntu 22.04 LTS 中添加、删除和授予用户 Sudo 权限
- 2024-11-27 碎片时间学编程「83]:满足条件时从末尾删除列表元素
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-