几个月前,我在 Hacker News 上(就像大家一样),偶然发现了一篇(现在已删除)关于不使用if
语句的文章。如果您不熟悉这个概念(就像我以前一样),您将获得真正的享受。只需在 Hacker News 上搜索“if 语句”。您将看到建议您可能不需要它们的文章,将它们称为代码异味的文章,甚至还有经典的“被认为是有害的”文章。听着,当人们开始建议使用某个编程概念实际上会伤害某人时,你就知道这个概念是名副其实的了。

如果您觉得这还不够,总有“反If运动”。加入后,您将获得一个漂亮的横幅以及网站上的您的姓名。如果您加入的话。哦,多么甜蜜的讽刺。
我第一次遇到这种奇怪的“if 厌恶”现象时,觉得很有趣,但可能只是互联网上更多的人在生气。您只需搜索一次 Google,就能找到一个对任何事情都生气的人。比如这个讨厌小猫的人。小猫。
一段时间后,我观看了Linus Torvald 的 TED 访谈。在该访谈中,他展示了两张幻灯片。第一张幻灯片包含他认为“品味不佳”的代码。

第二张幻灯片是相同的代码,但按照 Linus 的说法,是“品味良好”的。

我意识到 Linus 是一位有点两极分化的人物,您可能不同意“品味良好”与“品味不佳”的说法。但我认为我们可以普遍认同第二张幻灯片更容易让人接受。它简洁明了,逻辑路径更少,并且不包含if
语句。我希望我的代码看起来像那样。它不必是什么天才算法(它永远不会是),但我认为它可以简洁,并记住 Smashing Pumpkins 乐队的 Billy Corgan 关于简洁的说法……
简洁就是神圣。而神是空虚的。就像我一样。
– Billy Corgan,“Zero”
太黑暗了!但这是一张很棒的专辑。
除了使您的代码看起来杂乱无章之外,if
语句或“分支逻辑”还需要您的大脑同时保存和评估两条不同的路径,以及这些路径上可能发生的所有事情。如果您嵌套if
语句,则问题会加剧,因为您正在创建和跟踪决策树,而您的大脑必须像喝醉的猴子一样在整个树上跳来跳去。这类事情是导致代码难以阅读的原因。请记住,您应该在编写代码时考虑到接手您代码的傻瓜,他们将不得不维护它。而那个傻瓜很可能就是您自己。
作为我自己最喜欢的傻瓜,我最近一直在有意识地避免在我的 JavaScript 中编写if
语句。我并不总是能成功,但我注意到,至少,它迫使我从完全不同的角度思考解决问题的方法。这让我成为一名更好的开发人员,因为它迫使我参与我大脑的另一部分,否则它会坐在豆袋上吃花生 M&M's,而if
语句则完成所有工作。
在不编写if
语句的过程中,我发现了自己对 JavaScript 使用三元运算符和逻辑运算符来编写条件逻辑的方式的喜爱。我现在想向您建议的是,三元运算符的声誉不佳,您可以将其与&&
和||
运算符一起使用来编写一些非常简洁易读的代码。
备受争议的三元运算符
当我刚开始编程时,人们常说:“永远不要使用三元运算符。它们太复杂了。”所以我没有使用它们。从来没有。我从未使用过三元运算符。我甚至从未想过质疑这些人是否正确。
我认为他们错了。
三元运算符只是一行if
语句。建议它们在任何形式上都隐式地过于复杂,这仅仅是……不正确。我的意思是,我不是盒子里最愚蠢的甜甜圈,但我完全理解一个简单的三元运算符。当我们说始终避免使用它们时,我们是否只是在有点儿宠坏自己?我认为结构良好的三元运算符每次都胜过if
语句。
让我们举一个简单的例子。假设我们有一个应用程序,我们希望测试用户是否已登录。如果他们已登录,则将其发送到他们的个人资料页面。否则,将其发送到主页。以下是执行此操作的标准if
语句……
if (isLogggedIn) {
navigateTo('profile');
}
else {
navigateTo('unauthorized');
}
这是一个非常简单的操作,却需要六行代码来完成。六行。请记住,每次遍历一行代码时,您都必须记住上面出现的代码以及它如何影响下面的代码。
现在是三元运算符版本……
isLoggedIn ? navigateTo('profile') : navigateTo('unauthorized');
您的大脑只需要评估一行代码,而不是六行。您不必在各行之间移动,记住前一行是什么。
不过,三元运算符的缺点之一是您只能评估一个条件。从前面的示例出发,如果您希望在用户已登录时导航到个人资料页面,但在未登录时不执行任何操作,则此方法无效……
// !! Doesn't Compile !!
logggedIn ? navigateTo('profile')
您将不得不在这里写出一个实际的if
语句。或者您会吗?
当您只想评估条件的一侧并且不想使用if
语句时,您可以在 JavaScript 中使用一个技巧。您可以通过利用 JavaScript 使用||
(或)和&&
(和)运算符的方式来实现此目的。
loggedIn && navigateTo('profile');
它是如何工作的!?
我们在这里所做的是询问 JavaScript:“这两件事都成立吗?”如果第一项为假,则 JavaScript 虚拟机没有理由执行第二项。我们已经知道它们两者都不成立,因为其中一项为假。我们利用了这样一个事实,即如果第一项为假,JavaScript 将不会费心评估第二项。这相当于说:“如果第一个条件为真,则执行第二个条件。”
现在,如果我们想反过来呢?如果我们只想在用户未登录时导航到个人资料页面呢?您可以在loggedIn
变量前面加一个!
,但还有另一种方法。
loggedIn || navigateTo('profile');
这句话的意思是:“这两件事中任何一项都成立吗?”如果第一个为假,则它必须评估第二个以确保。但是,如果第一个为真,则它永远不会执行第二个,因为它已经知道其中一个为真;因此整个语句为真。
那么,这比只执行以下操作更好吗?
if (!loggedIn) navigateTo('profile');
不。以那种形式,它不是。但关键在于:一旦您知道可以使用&&
和||
运算符在if
语句之外评估相等性,就可以使用它们来极大地简化代码。
这是一个更复杂的示例。假设我们有一个登录函数,我们向其中传递一个用户对象。该对象可能为 null,因此我们需要检查本地存储以查看用户是否在其中保存了会话。如果他们有,并且他们是管理员用户,则将其定向到仪表板。否则,将其发送到一个页面,告诉他们未经授权。以下是作为直接的if
语句的外观。
function login(user) {
if (!user) {
user = getFromLocalStorage('user');
}
if (user) {
if (user.loggedIn && user.isAdmin) {
navigateTo('dashboard');
}
else {
navigateTo('unauthorized');
}
}
else {
navigateTo('unauthorized');
}
}
哎呦。这很复杂,因为我们正在对user
对象进行大量的 null 条件检查。我不希望这篇文章过于稻草人论证,因此让我们简化一下,因为这里有很多冗余代码,我们可能会将其重构为其他函数。
function checkUser(user) {
if (!user) {
user = getFromLocalStorage('user');
}
return user;
}
function checkAdmin(user) {
if (user.isLoggedIn && user.isAdmin) {
navigateTo('dashboard');
}
else {
navigateTo('unauthorized');
}
}
function login(user) {
if (checkUser(user)) {
checkAdmin(user);
}
else {
navigateTo('unauthorized');
}
}
主要的登录函数更简单,但这实际上是更多代码,并且在您考虑整体而不是仅仅login
函数时,不一定是“更简洁”的。
我想建议,如果我们放弃if
语句,拥抱三元运算符,并使用逻辑运算符来确定相等性,我们可以在两行代码中完成所有这些操作。
function login(user) {
user = user || getFromLocalStorage('user');
user && (user.loggedIn && user.isAdmin) ? navigateTo('dashboard') : navigateTo('unauthorized')
}
就是这样。if
语句产生的一切噪音都压缩成两行。如果第二行对您来说有点长且难以阅读,请将其包装起来,以便条件位于它们自己的行上。
function login(user) {
user = user || getFromLocalStorage("user");
user && (user.loggedIn && user.isAdmin)
? navigateTo("dashboard")
: navigateTo("unauthorized");
}
如果您担心下一个人可能不知道&&
和||
运算符如何在 JavaScript 中工作,请添加一些注释、一些空白和一棵快乐的树。释放你内心的鲍勃·罗斯。
function login(user) {
// if the user is null, check local storage to
// see if there is a saved user object there
user = user || getFromLocalStorage("user");
// Make sure the user is not null, and is also
// both logged in and an admin. Otherwise, DENIED. 🌲
user && (user.loggedIn && user.isAdmin)
? navigateTo("dashboard")
: navigateTo("unauthorized");
}
其他您可以做的事情
趁此机会,以下是一些您可以在 JavaScript 条件语句中使用的其他技巧。
赋值
我最喜欢的技巧之一(我在上面使用过),是用一行代码检查项目是否为 null,然后在为 null 时重新赋值。您可以使用||
运算符来实现此目的。
user = user || getFromLocalStorage('user');
您可以像这样一直进行下去……
user = user || getFromLocalStorage('user') || await getFromDatabase('user') || new User();
这也适用于三元运算符……
user = user ? getFromLocalStorage('user') : new User();
多个条件
你可以为三元运算符提供多个条件。例如,如果我们想要记录用户已登录然后进行导航,我们可以在不需要将所有这些抽象到另一个函数的情况下做到这一点。将其用括号括起来并用逗号分隔。
isLoggedIn ? (log('Logged In'), navigateTo('dashboard')) : navigateTo('unauthorized');
这对于你的&&
和||
运算符也适用……
isLoggedIn && (log('Logged In'), navigateTo('dashboard'));
嵌套三元表达式
你可以嵌套你的三元表达式。在他的关于三元运算符的精彩文章中,Eric Elliot 通过以下示例演示了这一点……
const withTernary = ({
conditionA, conditionB
}) => (
(!conditionA)
? valueC
: (conditionB)
? valueA
: valueB
);
Eric 在那里做的最有趣的事情是取反第一个条件,这样你就不会以问号和冒号放在一起结束,这使得它更难阅读。我会更进一步,添加一些缩进。我还添加了花括号和显式返回,因为看到一个括号然后立即出现另一个括号会让我的大脑开始预料一个永远不会出现的函数调用。
const withTernary = ({ conditionA, conditionB }) => {
return (
(!conditionA)
? valueC
: (conditionB)
? valueA
: valueB
)
}
作为一个一般规则,我认为你应该考虑不要嵌套三元表达式或if
语句。Hacker News 上的任何上述文章都会让你得出同样的结论。虽然我并不是在这里羞辱你,只是建议你也许(可能)以后会感谢自己没有这样做。
这就是我对被误解的三元运算符和逻辑运算符的看法。我认为它们可以帮助你编写干净、可读的代码,并完全避免使用if
语句。现在,如果我们能让 Linus Torvalds 批准所有这些都是“好品味”就好了。我就可以提前退休,安享余生。
我喜欢三元运算符的一点是它们是表达式而不是语句,因此它们只能应用于指令的一部分。因此,第一个示例可以进一步简化为
navigateTo(isLoggedIn ? 'profile' : 'unauthorized')
。哇!这真是太棒了!我不知道你可以这样做。
Noki Doki:谢谢!我也是这么想的!
三元运算符是一个运算符,而不是控制语句!它应该用于选择值,而不是选择操作!
用例
1. 执行相同的操作,但使用不同的值(Noki Doki 的示例)
2. 初始化变量,尤其是常量(Christopher Kirk-Nielsen 的示例)
是的,在我看来,这实际上是编写该行的更好方法。
事实上,一些风格指南/代码检查规则集禁止不属于语句或赋值的一部分的独立三元表达式。
我喜欢使用三元运算符进行变量赋值,例如
var togglerIsOpen = el.hasAttribute('open') ? true : false
(一个愚蠢的示例,甚至不需要三元运算符,但你明白了)。我也写过像你最后一个示例中那样的嵌套三元运算符。我严格地将其用于简短的变量赋值,同时尝试尊重不变性。只要它不太复杂,我就完全赞成它。我不同意你的帖子中的“代码越短越容易”的方法,例如
isLoggedIn && (log('Logged In'), navigateTo('dashboard'));
。这不容易阅读,并且不熟悉短路功能的人很容易迷路(我知道我会)。条件块可能更长,但对我来说,它更容易阅读、分解和理解。你可以清楚地了解发生了什么以及为什么发生,每个人都可以阅读它,并且你可以在不考虑整个事情是否仍然有效的情况下添加条件/操作。我认为了解仍然有效地缩短代码的方法是很棒的,但我让缩小器来担心这些。不过,我可以看到这对代码高尔夫非常有用!
我想,一如既往,这归结于个人喜好。仍然很高兴从这篇文章中学到了一些东西,所以谢谢! :)
附注:在关于
||
(我也喜欢使用!)的赋值部分,三元运算符的结果与上面||
行的结果不同。我相信它应该是user = user ? user : getFromLocalStorage('user') ? getFromLocalStorage('user') : new User();
对吧?——虽然不那么简洁。抓住了!谢谢。
我同意这一点,从某种意义上说,更简洁的代码并不等于更好的代码。通过在必要时尽早退出,我在使代码可读方面取得了更大的成功。
话虽如此,我非常喜欢使用三元运算符来设置初始值,如示例所示。
最大收获?始终质疑并使用正确的工具来完成工作。If 语句有其用途,三元运算符也有其用途。
刚刚发现了这篇文章,我注意到你仍然没有修复那行
user = user ? getFromLocalStorage('user') : new User();
,当我看到它时,它让我停顿了整整一分钟。只要我们为了只使用三元运算符而重复调用,完整的转换将是
user = user ? user : getFromLocalStorage('user') ? getFromLocalStorage('user') : await getFromDatabase('user') ? await getFromDatabase('user') : new User();
对吧?在你的isLoggedIn示例中,我更喜欢使用三元运算符来分配目标,然后进行导航,因为它将操作与决定操作周围条件的逻辑分开。因此
当然,任何一种都比
if... else...
结构好得多。感谢这篇文章!我是三元运算符的粉丝,所以同意所有这些。代码越少越好。
需要注意的一件事是在检查空值时
jim = jim || “默认值”
如果jim可能是数字或布尔值(我认为),则应避免这种情况。或者任何可能有效但为假的值。
正确,它捕获了JS中的所有7个假值:null、undefined、false、+0、-0、NaN和空字符串
好点!虽然我对类型问题的一个问题是,我从未遇到过它们。它们似乎比现实世界更具假设性。这是我对类型化语言的主要障碍。对我来说,这感觉有点像做准备工作。
有趣的阅读。不过,我不同意这一点。我的论点
如前一个评论中所述,代码越短并不等于代码越可读。
仅仅因为你可以做某事并不意味着你应该一直做它,或者说这是一个好主意。那个使用逗号表达式的示例在我看来仅仅是恶意的建议。
你很少可以说某种编码方式总是比另一种更好。大多数时候,你必须在多个选项之间做出决定,考虑每个选项在特定场景下的权衡。而且,我认为,三元表达式与if语句的情况就是一个很好的例子。
最后,在我看来,这段代码
无论如何都比这段代码更容易阅读和理解
是的——它本身更容易阅读。如果你有一个文件充满了这些,那么看起来就不那么有趣了。
我不同意这里的最后一点。第一个块更容易阅读。问题是它需要其他块才能发挥作用,因此要理解完整的逻辑和所有可能的副作用,你必须更改上下文并处理分支逻辑。对我来说,这比将所有内容都放在眼前更难。
同意,尽管我会对你的示例进行一个更改。我发现检查积极的事情比检查消极的事情更好。
这也具有默认情况下失败的好处,这将保护你免受可能允许访问但本不应该访问的错误。
谢谢,布拉德!我也更喜欢那样。
@布拉德,当你切换到“积极”检查时,你忘记将||切换回&&了。
我了解到,当语句适合一行(少于81个字符)时,三元运算符是一个不错的选择,并相应地使用了它。
那么,这是否意味着我在最近一段时间内注意到的所有反if噪音实际上仅仅是关于风格,而不是在逻辑意义上避免“if”?什么鬼?感觉像来回的时尚谈话。
我认为这都是很火的观点。你可以把这篇文章也看作是另一个很火的观点。
仅仅因为你可以做到并不意味着你应该去做。有些情况下三元运算符很棒,但在非常简单的变量赋值之外,它们会很快使代码的可读性大大降低。代码的可读性,对你自己,对同事,对你代码的其他用户来说,都比因为写了一些简洁而酷炫的东西而感觉自己很聪明要重要得多。
我同意大型if语句和大量分歧路径可能令人不快,但这正是你退一步更仔细地思考代码中逻辑路径的时候,而不是因为某种东西在视觉上更令人愉悦而完全转向更令人困惑的东西。
非常正确。
我想这就像一个钟摆。在中间的某个地方,存在着平衡。出于某种原因,我们喜欢大幅摆动到一侧或另一侧。
很棒的文章。只有一件事
使用withTernary的正确代码(对我来说有效)是
还有赋值
…
x && (y=2);
请注意,我再怎么强调都不为过,如果虚假值是有效值,请不要那样使用逻辑运算符
如果要避免使用
if
语句,我们应该转向更逻辑化和更高级的编程结构,而不是把旧酒装进新瓶。这是对的!你说得对。三元运算符不是条件语句的替代品,因为它本身就是一个条件语句。
那些批判
if
的人要来找我了!告诉我的妻子和孩子们我爱他们。“您可以为三元运算符提供多个条件。”我以前不知道这可能。如果三元运算符需要做不止一件事情,我的默认做法一直是将其移动到if语句中。括号和逗号改变了游戏规则。非常棒的技巧和文章,谢谢!
不错!说实话,在开始为这篇文章做研究之前,我也不知道这一点。
如果像
if
语句那样对嵌套的三元运算符进行样式设置,我发现它们更容易阅读我同意你对三元运算符所说的一切,但不喜欢仅仅为了副作用而使用表达式求值。所以
更喜欢
不喜欢
第二个示例是一个表达式,它应该是纯的,并且不应导致副作用。
语句,例如独立的函数调用或赋值,是为了其效果而执行的。
感谢这篇文章!读起来很棒!由于在上面的例子中,我们只使用了对象和函数。如果我们也阐明一下使用
Array.prototype.some()
和Array.prototype.every()
等便捷方法在数组中抽象条件并在其中使用三元运算符,那就太好了。这将是对本文的一个很好的补充! :)这些很棒的技巧只适用于JS吗?
我真的很喜欢阅读这篇文章。
我认为代码看起来简洁优雅。
阅读评论非常重要,否则你会错过Noki Doki和Kirk-Nielsen的贡献,正如Matt Seitz在两个主要结论中总结的那样。
因此,如果文章更新了包括建议的改进,并对他们的贡献表示感谢,那就太好了。
谢谢大家。
我想指出,||不会检查项目是否为空。相反,它检查项目是否为真值。这是一个非常重要的细节,需要牢记在心
var item = 0;
item = item || 1;
console.log(item); // -> 1
“三元运算符的一个缺点是,您不能只对一个条件进行评估。”
这可以简化为
logggedIn ? navigateTo(‘profile’) : null;
这绝对不如简单地写
if (loggedIn) navigateTo('profile')
它仅仅是为了避免
if
语句,并用实际上是if....else
结构的替代且不太明确的语法来替换它,而如果你不需要else
部分,就没有必要使用if...else
。此外,它将表达式用作语句,这是不应该做的。使用三元运算符会创建一个计算结果为值的表达式,而不是语句,并且你永远不会在不将其赋值给变量或传递给函数调用的情况下只写一个值。
所以你应该这样做
navigateTo(loggedIn ? 'profile' : 'login')
或者这样
const redirect = loggedIn ? 'profile' : 'login'
每当我看到有人贬低三元运算符并抱怨它们据称难以阅读时,这都告诉我那个人可能对javascript没有很好的理解。类似于使用类而不是学习工厂函数和组合。我一直在使用三元运算符,只要我能够。以下是我给那些难以阅读三元运算符的人的建议:大声说出它们。例如:“用户是否已登录?如果是,则将他们带到仪表板,否则,将他们带到其他地方”(logged_id ? dashboard() : logInPage())
这是一篇很棒的文章。感谢您将其发布到社区。在我看来,通过创建更小、更简洁的代码来使用函数式代码最佳实践一直是我体验中无错误代码的常见主题。太棒了!
文章中的这个三元表达式
isLoggedIn ? navigateTo('profile') : navigateTo('unauthorized');
像这样写更好
navigateTo(isLoggedIn ? ‘profile’ : ‘unauthorized’);
我认为它更好,因为它清楚地表明您将要
navigateTo
,唯一的问题是去哪里。您实际上可以使用ES6的默认函数参数避免user = user || getFromLocalStorage(“user”),从而进一步提高可读性和减少代码长度
function login(user = getFromLocalStorage(“user”)) {
….
}
嘿,伯克,有趣的话题!
我只是想指出,“好的品味”是高度主观的,你的部分文章也是如此。
正如你已经说过的,我也认为编写代码时主要关注的是使其可读。虽然没有一种方法可以使其成为最佳可读性,但不幸的是,有许多“不太好”的方法可以提高可读性……我认为其中之一就是三元运算符
我个人经常使用三元运算符,但正如其他人已经在评论中提到的,它并非旨在用作
if
的替代品。我将其用于变量赋值,仅此而已。你可以尽可能地写出“聪明”的代码,并将所有表达式整理成两行,但这在下次阅读时不会帮助你……实际上会让你头疼,因为你必须在阅读时重新思考你的代码。
我想说的是,你可以使用
if
语句编写完全可读的代码,而且可能比使用大量内联表达式更简洁。例如,我会将你的第一个充斥着4个if
(也嵌套)的函数重写如下一旦你将变量赋值与实际的函数调用分离开来,如果你愿意,你可以轻松地将该if替换为三元运算符
看起来与你的三元重写非常相似,不是吗?
我认为
if
语句和三元
运算符之间不应该存在战争……你都可以用它们写出糟糕的代码。你只需要注意使用你所拥有的所有工具(如命名和间距)来简化代码,使其易于阅读和维护(即使这意味着你的函数将在多行中生成)。另外值得一提的是,从测试的角度来看,if语句似乎更容易测试
继续努力,总的来说这是一篇不错的文章!✌️
我不会称之为技巧,更像是一种技术,即短路[布尔]运算。JavaScript 程序员很幸运能够使用它。
参见 https://en.wikipedia.org/wiki/Short-circuit_evaluation
我也喜欢三元运算符 :)
之前不知道
&&
和||
的技巧,所以当时我的认知被颠覆了。然后当我读到 Noki Doki 关于navigateTo(isLoggedIn ? 'profile' : 'unauthorized')
的评论时,再次被颠覆了 :)由于我们正努力降低复杂度并提高可读性,我感到有责任谨小慎微地指出,在以下语句中,括号完全没有必要,因为逻辑运算符是相同的
当然,如果它看起来像这样,那么它们将是必需的
既然我正在吹毛求疵,我也应该指出我犯了一个错误,称之为语句——它是一个表达式!