编写更好的JavaScript条件式和匹配条件的技巧(小结)


Posted in Javascript onJune 27, 2019

介绍

如果你像我一样乐于见到整洁的代码,那么你会尽可能地减少代码中的条件语句。通常情况下,面向对象编程让我们得以避免条件式,并代之以继承和多态。我认为我们应当尽可能地遵循这些原则。

正如我在另一篇文章 JavaScript 整洁代码的最佳实践里提到的,你写的代码不单单是给机器看的,还是给“未来的自己”以及“其他人”看的。

从另一方面来说,由于各式各样的原因,可能我们的代码最终还是会有条件式。也许是修复 bug 的时间很紧,也许是不使用条件语句会对我们的代码库造成大的改动,等等。本文将会解决这些问题,同时帮助你组织所用的条件语句。

技巧

以下是关于如何构造 if...else 语句以及如何用更少的代码实现更多功能的技巧。阅读愉快!

1. 要事第一。小细节,但很重要

不要使用否定条件式(这可能会让人感到疑惑)。同时,使用条件式简写来表示 boolean 值。这个无须再强调了,尤其是否定条件式,这不符合正常的思维方式。

不好的:

const isEmailNotVerified = (email) => { 
// 实现 
} 
if (!isEmailNotVerified(email)) { 
// 做一些事... 
} 
if (isVerified === true) { 
// 做一些事... 
}

好的:

const isEmailVerified = (email) => { 
// 实现 
} 
if (isEmailVerified(email)) { 
// 做一些事... 
} 
if (isVerified) { 
// 做一些事... 
}

现在,理清了上面的事情后,我们就可以开始了。

2. 对于多个条件,使用 Array.includes

假设我们想要在函数中检查汽车模型是 renault 还是 peugeot。那么代码可能是这样的:

const checkCarModel = (model) => { 
if(model === 'renault' || model === 'peugeot') { 
console.log('model valid'); 
} 
} 
checkCarModel('renault'); // 输出 'model valid'

考虑到我们只有两个模型,这么做似乎也还能接受,但如果我们还想要检查另一个或者是几个模型呢?如果我们增加更多 or 语句,那么代码将变得难以维护,且不够整洁。为了让它更加简洁,我们可以像这样重写函数:

const checkCarModel = (model) => { 
if(['peugeot', 'renault'].includes(model)) { 
console.log('model valid'); 
} 
} 
checkCarModel('renault'); // 输出 'model valid'

上面的代码看起来已经很漂亮了。为了更进一步改善它,我们可以创建一个变量来存放汽车模型:

const checkCarModel = (model) => { 
const models = ['peugeot', 'renault']; 
if(models.includes(model)) { 
console.log('model valid'); 
} 
} 
checkCarModel('renault'); // 输出 'model valid'

现在,如果我们想要检查更多模型,只需要添加一个新的数组元素即可。此外,如果它很重要的话,我们还可以将 models 变量定义在函数作用域外,并在需要的地方重用。这种方式可以让我们集中管理,并使维护变得轻而易举,因为我们只需在代码中更改一个位置。

3. 匹配所有条件,使用 Array.every 或者 Array.find

在本例中,我们想要检查每个汽车模型是否都是传入函数的那一个。为了以更加命令式的方式实现,我们会这么做:

const cars = [ 
{ model: 'renault', year: 1956 }, 
{ model: 'peugeot', year: 1968 }, 
{ model: 'ford', year: 1977 } 
]; 
const checkEveryModel = (model) => { 
let isValid = true; 
for (let car of cars) { 
if (!isValid) { 
break; 
} 
isValid = car.model === model; 
} 
return isValid; 
} 
console.log(checkEveryModel('renault')); // 输出 false

如果你更喜欢以命令式的风格行事,上面的代码或许还不错。另一方面,如果你不关心其背后发生了什么,那么你可以重写上面的函数并使用 Array.every 或者 Array.find 来达到相同的结果。

const checkEveryModel = (model) => { 
return cars.every(car => car.model === model); 
} 
console.log(checkEveryModel('renault')); // 输出 false

通过使用 Array.find 并做轻微的调整,我们可以达到相同的结果。两者的表现是一致的,因为两个函数都为数组中的每一个元素执行了回调,并且在找到一个 falsy 项时立即返回 false。

const checkEveryModel = (model) => { 
return cars.find(car => car.model !== model) === undefined; 
} 
console.log(checkEveryModel('renault')); // 输出 false

4. 匹配部分条件,使用 Array.some

Array.every 匹配所有条件,这个方法则可以轻松地检查我们的数组是否包含某一个或某几个元素。为此,我们需要提供一个回调并基于条件返回一个布尔值。

我们可以通过编写一个类似的 for...loop 语句来实现相同的结果,就像之前写的一样。但幸运的是,有很酷的 JavaScript 函数可以来帮助我们完成这件事。

const cars = [ 
{ model: 'renault', year: 1956 }, 
{ model: 'peugeot', year: 1968 }, 
{ model: 'ford', year: 1977 } 
]; 
const checkForAnyModel = (model) => { 
return cars.some(car => car.model === model); 
} 
console.log(checkForAnyModel('renault')); // 输出 true

5. 提前返回而不是使用 if...else 分支

当我还是学生的时候,就有人教过我:一个函数应该只有一个返回语句,并且只从一个地方返回。如果细心处理,这个方法倒也还好。我这么说也就意味着,我们应该意识到它在某些情况下可能会引起条件式嵌套地狱。如果不受控制,多个分支和 if...else 嵌套将会让我们感到很痛苦。

另一方面,如果代码库很大且包含很多行代码,位于深层的一个返回语句可能会带来问题。现在我们都实行关注点分离和 SOLID 原则,因此,代码行过多这种情况挺罕见的。

举例来解释这个问题。假设我们想要显示所给车辆的模型和生产年份:

const checkModel = (car) => { 
let result; // 首先,定义一个 result 变量 
// 检查是否有车 
if(car) { 
// 检查是否有车的模型 
if (car.model) { 
// 检查是否有车的年份 
if(car.year) { 
result = `Car model: ${car.model}; Manufacturing year: ${car.year};`; 
} else { 
result = 'No car year'; 
} 
} else { 
result = 'No car model' 
} 
} else { 
result = 'No car'; 
} 
return result; // 我们的单独的返回语句 
} 
console.log(checkModel()); // 输出 'No car' 
console.log(checkModel({ year: 1988 })); // 输出 'No car model' 
console.log(checkModel({ model: 'ford' })); // 输出 'No car year' 
console.log(checkModel({ model: 'ford', year: 1988 })); // 输出 'Car model: ford; Manufacturing year: 1988;'

正如你所看到的,即使本例的问题很简单,上面的代码也实在太长了。可以想象一下,如果我们有更加复杂的逻辑会发生什么事。大量的 if...else 语句。

我们可以重构上面的函数,分解成多个步骤并稍做改善。例如,使用三元操作符,包括 && 条件式等。不过,这里我直接跳到最后,向你展示借助现代 JavaScript 特性和多个返回语句,代码可以有多简洁。

const checkModel = ({model, year} = {}) => { 
if(!model && !year) return 'No car'; 
if(!model) return 'No car model'; 
if(!year) return 'No car year'; 
// 这里可以任意操作模型或年份 
// 确保它们存在 
// 无需更多检查 
// doSomething(model); 
// doSomethingElse(year); 
return `Car model: ${model}; Manufacturing year: ${year};`; 
} 
console.log(checkModel()); // 输出 'No car' 
console.log(checkModel({ year: 1988 })); // 输出 'No car model' 
console.log(checkModel({ model: 'ford' })); // 输出 'No car year' 
console.log(checkModel({ model: 'ford', year: 1988 })); // 输出 'Car model: ford; Manufacturing year: 1988;'

在重构版本中,我们包含了解构和默认参数。默认参数确保我们在传入 undefined 时有可用于解构的值。注意,如果传入 null ,函数将会抛出错误。这也是之前那个方法的优点所在,因为那个方法在传入 null 的时候会输出 'No car'。

对象解构确保函数只取所需。例如,如果我们在给定车辆对象中包含额外属性,则该属性在我们的函数中是无法获取的。

根据偏好,开发者会选择其中一种方式。实践中,编写的代码通常介于两者之间。很多人觉得 if...else 语句更容易理解,并且有助于他们更为轻松地遵循程序流程。

6. 使用索引或者映射,而不是 switch 语句

假设我们想要基于给定的国家获取汽车模型。

const getCarsByState = (state) => { 
switch (state) { 
case 'usa': 
return ['Ford', 'Dodge']; 
case 'france': 
return ['Renault', 'Peugeot']; 
case 'italy': 
return ['Fiat']; 
default: 
return []; 
} 
} 
console.log(getCarsByState()); // 输出 [] 
console.log(getCarsByState('usa')); // 输出 ['Ford', 'Dodge'] 
console.log(getCarsByState('italy')); // 输出 ['Fiat']

上诉代码可以重构,完全去除 switch 语句。

const cars = new Map() 
.set('usa', ['Ford', 'Dodge']) 
.set('france', ['Renault', 'Peugeot']) 
.set('italy', ['Fiat']); 
const getCarsByState = (state) => { 
return cars.get(state) || []; 
} 
console.log(getCarsByState()); // 输出 [] 
console.log(getCarsByState('usa')); //输出 ['Ford', 'Dodge'] 
console.log(getCarsByState('italy')); // 输出 ['Fiat']

或者,我们还可以为包含可用汽车列表的每个国家创建一个类,并在需要的时候使用。不过这个就是题外话了,本文的主题是关于条件句的。更恰当的修改是使用对象字面量。

const carState = { 
usa: ['Ford', 'Dodge'], 
france: ['Renault', 'Peugeot'], 
italy: ['Fiat'] 
}; 
const getCarsByState = (state) => { 
return carState[state] || []; 
} 
console.log(getCarsByState()); // 输出 [] 
console.log(getCarsByState('usa')); // 输出 ['Ford', 'Dodge'] 
console.log(getCarsByState('france')); // 输出 ['Renault', 'Peugeot']

7. 使用自判断链接和空合并

到了这一小节,我终于可以说“最后”了。在我看来,这两个功能对于 JavaScript 语言来说是非常有用的。作为一个来自 C# 世界的人,可以说我经常使用它们。

在写这篇文章的时候,这些还没有得到完全的支持。因此,对于以这种方式编写的代码,你需要使用 Babel 进行编译。你可以在自判断链接这里以及在空合并这里查阅。

自判断链接允许我们在没有显式检查中间节点是否存在的时候处理树形结构,空合并可以确保节点不存在时会有一个默认值,配合自判断链接使用会有不错的效果。

让我们用一些例子来支撑上面的结论。一开始,我们还是用以前的老方法:

const car = { 
model: 'Fiesta', 
manufacturer: { 
name: 'Ford', 
address: { 
street: 'Some Street Name', 
number: '5555', 
state: 'USA' 
} 
} 
} 
// 获取汽车模型 
const model = car && car.model || 'default model'; 
// 获取厂商地址 
const street = car && car.manufacturer && car.manufacturer.address && car.manufacturer.address.street || 'default street'; 
// 请求一个不存在的属性 
const phoneNumber = car && car.manufacturer && car.manufacturer.address && car.manufacturer.phoneNumber; 
console.log(model) // 输出 'Fiesta' 
console.log(street) // 输出 'Some Street Name' 
console.log(phoneNumber) // 输出 undefined

因此,如果我们想要知道厂商是否来自 USA 并将结果打印,那么代码是这样的:

const checkCarManufacturerState = () => { 
if(car && car.manufacturer && car.manufacturer.address && car.manufacturer.address.state === 'USA') { 
console.log('Is from USA'); 
} 
} 
checkCarManufacturerState() // 输出 'Is from USA'

我无需再赘述如果对象结构更加复杂的话,代码会多么混乱了。许多库,例如 lodash,有自己的函数作为替代方案。不过这不是我们想要的,我们想要的是在原生 js 中也能做同样的事。我们来看一下新的方法:

// 获取汽车模型 
const model = car?.model ?? 'default model'; 
// 获取厂商地址 
const street = car?.manufacturer?.address?.street ?? 'default street'; 
// 检查汽车厂商是否来自 USA 
const checkCarManufacturerState = () => { 
if(car?.manufacturer?.address?.state === 'USA') { 
console.log('Is from USA'); 
} 
}

这看起来更加漂亮和简洁,对我来说,非常符合逻辑。如果你想知道为什么应该使用 ?? 而不是 || ,只需想一想什么值可以当做 true 或者 false,你将可能有意想不到的输出。

顺便说句题外话。自判断链接同样支持 DOM API,这非常酷,意味着你可以这么做:

const value = document.querySelector('input#user-name')?.value;

结论

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript css float属性的特殊写法
Nov 13 Javascript
Ext JS Grid在IE6 下宽度的问题解决方法
Feb 15 Javascript
jQuery实现固定在网页顶部的菜单效果代码
Sep 02 Javascript
jquery实现浮动在网页右下角的彩票开奖公告窗口代码
Sep 04 Javascript
需要牢记的JavaScript基础知识
Sep 25 Javascript
jquery+css3问卷答题卡翻页动画效果示例
Oct 26 Javascript
jquery.multiselect多选下拉框实现代码
Nov 11 Javascript
Bootstrap表单制作代码
Mar 17 Javascript
JavaScript 函数的定义-调用、注意事项
Apr 16 Javascript
JS中去掉array中重复元素的方法
May 26 Javascript
JavaScript遍历数组的三种方法map、forEach与filter实例详解
Feb 27 Javascript
通过微信公众平台获取公众号文章的方法示例
Dec 25 Javascript
原生js实现抽奖小游戏
Jun 27 #Javascript
JavaScript实现图片放大镜效果
Jun 27 #Javascript
详解Jest结合Vue-test-utils使用的初步实践
Jun 27 #Javascript
微信小程序自定义组件实现环形进度条
Nov 17 #Javascript
vue + typescript + 极验登录验证的实现方法
Jun 27 #Javascript
JS前端知识点 运算符优先级,URL编码与解码,String,Math,arguments操作整理总结
Jun 27 #Javascript
JS前端知识点offset,scroll,client,冒泡,事件对象的应用整理总结
Jun 27 #Javascript
You might like
php操作csv文件代码实例汇总
2014/09/22 PHP
深入了解PHP中的Array数组和foreach
2016/11/06 PHP
PHP实现转盘抽奖算法分享
2020/04/15 PHP
实例讲解php将字符串输出到HTML
2019/01/27 PHP
PHP dirname(__FILE__)原理及用法解析
2020/10/28 PHP
音乐播放用的的几个函数
2006/09/07 Javascript
jQuery 图像裁剪插件Jcrop的简单使用
2009/05/22 Javascript
javascript EXCEL 操作类代码
2009/07/30 Javascript
jQuery获取地址栏参数插件(模仿C#)
2010/10/26 Javascript
javascript中的取反再取反~~没有意义
2014/04/06 Javascript
JavaScript检查某个function是否是原生代码的方法
2014/08/20 Javascript
浅谈Javascript中的Function与Object
2015/01/26 Javascript
javascript实现日期按月份加减
2015/05/15 Javascript
基于jquery css3实现点击动画弹出表单源码特效
2015/08/31 Javascript
Zero Clipboard实现浏览器复制到剪贴板的方法(多个复制按钮)
2016/03/24 Javascript
jquery Form轻松实现文件上传
2017/05/24 jQuery
Vue 2.0的数据依赖实现原理代码简析
2017/07/10 Javascript
JS使用正则表达式找出最长连续子串长度
2017/10/26 Javascript
JS严格模式知识点总结
2018/02/27 Javascript
深入浅析Node环境和浏览器的区别
2018/08/14 Javascript
详解如何配置vue-cli3.0的vue.config.js
2018/08/23 Javascript
vue-cli脚手架的安装教程图解
2018/09/02 Javascript
vue element 关闭当前tab 跳转到上一路由操作
2020/07/22 Javascript
vue常用高阶函数及综合实例
2021/02/25 Vue.js
[49:18]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 OG vs TNC
2018/04/01 DOTA
Python pickle类库介绍(对象序列化和反序列化)
2014/11/21 Python
PyQt5每天必学之像素图控件QPixmap
2018/04/19 Python
Python基于win32ui模块创建弹出式菜单示例
2018/05/09 Python
Python代码块及缓存机制原理详解
2019/12/13 Python
matplotlib部件之矩形选区(RectangleSelector)的实现
2021/02/01 Python
一款基于css3和jquery实现的动画显示弹出层按钮教程
2015/01/04 HTML / CSS
美国玛丽莎收藏奢华时尚商店:Marissa Collections
2016/11/21 全球购物
“型”走纽约上东区:Sam Edelman
2017/04/02 全球购物
慈善晚会策划方案
2014/05/14 职场文书
求职自我推荐信
2014/06/25 职场文书
海贼王十大潜力果实,路飞仅排第十,第一可毁世界(震震果实)
2022/03/18 日漫