编写更好的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 相关文章推荐
JS动画效果代码3
Apr 03 Javascript
js 中 document.createEvent的用法
Aug 29 Javascript
用jQuery实现的智能隐藏、滑动效果的返回顶部代码
Mar 18 Javascript
原生javascript实现图片按钮切换
Jan 12 Javascript
javascript面向对象之访问对象属性的两种方式分析
Jan 13 Javascript
详解JavaScript中shift()方法的使用
Jun 09 Javascript
两款JS脚本判断手机浏览器类型跳转WAP手机网站
Oct 16 Javascript
js验证手机号、密码、短信验证码代码工具类
Jun 24 Javascript
React中常见的动画实现的几种方式
Jan 10 Javascript
elementUI 动态生成几行几列的方法示例
Jul 11 Javascript
Layui数据表格之单元格编辑方式
Oct 26 Javascript
基于JavaScript实现十五拼图代码实例
Apr 26 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
从一个不错的留言本弄的mysql数据库操作类
2007/09/02 PHP
php定时计划任务的实现方法详解
2013/06/06 PHP
使用淘宝IP库获取用户ip地理位置
2013/10/27 PHP
解决laravel查询构造器中的别名问题
2019/10/17 PHP
ExtJS 2.0实用简明教程之应用ExtJS
2009/04/29 Javascript
jQuery实现id模糊查询的小例子
2013/03/19 Javascript
jQuery窗口、文档、网页各种高度的精确理解
2014/07/02 Javascript
js如何判断访问是来自搜索引擎(蜘蛛人)还是直接访问
2015/09/14 Javascript
各式各样的导航条效果css3结合jquery代码实现
2016/09/17 Javascript
yarn与npm的命令行小结
2016/10/20 Javascript
微信小程序 高德地图SDK详解及简单实例(源码下载)
2017/01/11 Javascript
使用vue的v-for生成table并给table加上序号的实例代码
2017/10/27 Javascript
AngularJS实现的2048小游戏功能【附源码下载】
2018/01/03 Javascript
webpack将js打包后的map文件详解
2018/02/22 Javascript
一次微信小程序内地图的使用实战记录
2019/09/09 Javascript
vue新建项目并配置标准路由过程解析
2019/12/09 Javascript
微信小程序自定义模态弹窗组件详解
2019/12/24 Javascript
JavaScript实现移动小精灵的案例代码
2020/12/12 Javascript
在Python中用GDAL实现矢量对栅格的切割实例
2020/03/11 Python
Python Flask上下文管理机制实例解析
2020/03/16 Python
Python网页解析器使用实例详解
2020/05/30 Python
多个版本的python共存时使用pip的正确做法
2020/10/26 Python
Numpy数组的广播机制的实现
2020/11/03 Python
BeautifulSoup中find和find_all的使用详解
2020/12/07 Python
python爬虫今日热榜数据到txt文件的源码
2021/02/23 Python
python学习之使用Matplotlib画实时的动态折线图的示例代码
2021/02/25 Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
2021/03/04 Python
浅析HTML5中的 History 模式
2017/06/22 HTML / CSS
JAVA程序设计笔试题面试题一套
2015/07/28 面试题
梅花魂教学反思
2014/04/25 职场文书
2014年中秋寄语
2014/08/11 职场文书
秋季运动会开幕词
2015/01/28 职场文书
2015年“7.11”世界人口日宣传活动方案
2015/05/06 职场文书
困难补助申请报告
2015/05/19 职场文书
初三数学教学反思
2016/02/17 职场文书
Java版 简易五子棋小游戏
2022/05/04 Java/Android