译者:@chorer

译文:https://chorer.github.io/2019/06/24/Trs-更好的JavaScript条件式和匹配标准技巧/
作者:@Milos Protic
原文:https://devinduct.com/blogpost/35/tips-and-tricks-for-better-javascript-conditionals-and-match-criteria

介绍

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

正如我在另一篇文章 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,这非常酷,意味着你可以这么做:


▼原创系列推荐▼1.JavaScript 重温系列(22篇全)
2.ECMAScript 重温系列(10篇全)
3.JavaScript设计模式 重温系列(9篇全)
4.正则 / 框架 / 算法等 重温系列(16篇全)

你点的每个赞,我都认真当成了喜欢

【JS】303- 编写更好的 JavaScript 条件式和匹配条件的技巧的更多相关文章

  1. 使用 Promises 编写更优雅的 JavaScript 代码

    你可能已经无意中听说过 Promises,很多人都在讨论它,使用它,但你不知道为什么它们如此特别.难道你不能使用回调么?有什么了特别的?在本文中,我们一起来看看 Promises 是什么以及如何使用它 ...

  2. CSS vs. JS Animation: 哪个更快

    CSS vs. JS Animation: 哪个更快? CSS vs. JS Animation: 哪个更快? 基于JavaScript的动画竟然已经默默地比CSS的transition动画快了?而且 ...

  3. CSS3动画 相比JS Animation 哪个更快?

    CSS vs. JS Animation: 哪个更快? 基于JavaScript的动画竟然已经默默地比CSS的transition动画快了?而且,Adobe和 Google竟然一直在发布可以媲美原生应 ...

  4. JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  5. 新书《编写可测试的JavaScript代码 》出版,感谢支持

    本书介绍 JavaScript专业开发人员必须具备的一个技能是能够编写可测试的代码.不管是创建新应用程序,还是重写遗留代码,本书都将向你展示如何为客户端和服务器编写和维护可测试的JavaScript代 ...

  6. Type.js – 帮助你更好的控制网页字体排版

    Type.js 是一款很好的网页字体排版工具.它可以让你使用新的 CSS 属性,在网页上试下更精细的排版控制.设置很简单.上传 type.js 到您的网站,并在你的 HTML 链接中引用.接下来,你就 ...

  7. 编写可测试的JavaScript代码

    <编写可测试的JavaScript代码>基本信息作者: [美] Mark Ethan Trostler 托斯勒 著 译者: 徐涛出版社:人民邮电出版社ISBN:9787115373373上 ...

  8. 《编写可维护的javascript》读书笔记(中)——编程实践

    上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...

  9. 14条最佳JS代码编写技巧

    http://gaohaixian.blog.163.com/blog/static/123260105201142645458315/写任何编程代码,不同的开发者都会有不同的见解.但参考一下总是好的 ...

随机推荐

  1. hdu 1509 Windows Message Queue (优先队列)

    Windows Message QueueTime Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Oth ...

  2. gopls替换hover文档

    一直在用vscode来开发golang程序,也一直在用 gopls语言服务器,也一直用鼠标悬浮显示函数的文档. 今天 偶然关闭了 gopls,然后 鼠标悬浮后,发现了 新大陆,nima,gopls的 ...

  3. 解放双手,在PC端进行Android真机调试

    scrcpy简介(拼写是scrcpy,非Python爬虫框架Scrapy) 简单地来说,scrcpy就是通过adb调试的方式来将手机屏幕投到电脑上,并可以通过电脑控制您的Android设备.它可以通过 ...

  4. Vue学习笔记:Vuex

    为什么需要Vuex 管理共享状态 解决一份数据在多个组件中试用的困难 系统化的状态管理,区别于小型状态过来 底层设计模式: 全局单例模式 应用场景 适合中大型项目: 小项目反而会因为引入更多概念和框架 ...

  5. PostGIS 爆管分析之找出总阀门

    这个算法算是被摒弃了,但是很多自己思考过后留下的成果,虽然不用了,留着做记录. 算法目的是为了发生爆管后找到总阀门,这里分了几个步骤: 1.找到爆管点所在管段 2.通过遍历找到爆管点所有影响的阀门 3 ...

  6. PostGIS 结合Openlayers以及Geoserver实现最短路径分析(二)

    前文讲述了怎么用ArcMap制作了测试数据,并导入了PostGIS,接下来我们需要结合PgRouting插件,对入库的数据再进行一下处理. 1.在pgAdmin中,执行下面的sql语句 --添加起点字 ...

  7. day 17 re模块 正则表达式

    import re    引用re模块 查找 finall:匹配所有,每一项都是列表中的一个元素 search:只匹配从左到右的第一个,得到的不是直接的结果而是一个变量,通过group方法获取结果,没 ...

  8. scrapy抓取斗鱼APP主播信息

    如何进行APP抓包 首先确保手机和电脑连接的是同一个局域网(通过路由器转发的网络,校园网好像还有些问题). 1.安装抓包工具Fiddler,并进行配置 Tools>>options> ...

  9. moco框架应用一步到位

    1.  Moco部署 5.1         运行环境 ü   Java运行环境 ü   moco-runner-0.11.0-standalone.jar jar包: Windows Java环境配 ...

  10. 【Android - 控件】之MD - Snackbar的使用

    Snackbar 是 Android 5.0 新特性——Material Design 中的一个控件,用来代替 Toast ,Snackbar与Toast的主要区别是:Snackbar可以滑动退出,也 ...