编写更好的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 相关文章推荐
jquery中邮箱地址 URL网站地址正则验证实例代码
Sep 15 Javascript
js单独获取一个checkbox看其是否被选中
Sep 22 Javascript
JavaScript中的alert()函数使用技巧详解
Dec 29 Javascript
向JavaScript的数组中添加元素的方法小结
Oct 24 Javascript
JavaScript实现数组降维详解
Jan 05 Javascript
webpack打包后直接访问页面图片路径错误的解决方法
Jun 17 Javascript
ES6中数组array新增方法实例总结
Nov 07 Javascript
jQuery实现获取form表单内容及绑定数据到form表单操作分析
Jul 03 jQuery
vue 自定义指令自动获取文本框焦点的方法
Aug 25 Javascript
如何基于layui的laytpl实现数据绑定的示例代码
Apr 10 Javascript
浅谈element中InfiniteScroll按需引入的一点注意事项
Jun 05 Javascript
js中Map和Set的用法及区别实例详解
Feb 15 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显示Facebook的粉丝数量方法
2014/01/08 PHP
PHP易混淆知识整理笔记
2015/09/24 PHP
PHP7正式版测试,性能惊艳!
2015/12/08 PHP
Yii框架通过请求组件处理get,post请求的方法分析
2019/09/03 PHP
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
2020/12/16 PHP
类似GMAIL的Ajax信息反馈显示
2010/02/16 Javascript
一些主流JS框架中DOMReady事件的实现小结
2011/02/12 Javascript
完美解决IE低版本不支持call与apply的问题
2013/12/05 Javascript
js中文逗号转英文实现
2014/02/11 Javascript
js 绑定键盘鼠标事件示例代码
2014/02/12 Javascript
JS对文本框值的判断示例
2014/03/10 Javascript
js实现上传图片预览的方法
2015/02/09 Javascript
jQuery 1.9.1源码分析系列(十)事件系统之绑定事件
2015/11/19 Javascript
Nodejs express框架一个工程中同时使用ejs模版和jade模版
2015/12/28 NodeJs
Bootstrap表单布局样式源代码
2016/07/04 Javascript
原生JS实现获取及修改CSS样式的方法
2018/09/04 Javascript
Vue函数式组件的应用实例详解
2019/08/30 Javascript
Vue自定义组件的四种方式示例详解
2020/02/28 Javascript
Python魔术方法详解
2015/02/14 Python
在Python的while循环中使用else以及循环嵌套的用法
2015/10/14 Python
记一次python 内存泄漏问题及解决过程
2018/11/29 Python
Python搭建代理IP池实现接口设置与整体调度
2019/10/27 Python
python统计字符串中字母出现次数代码实例
2020/03/02 Python
浅谈tensorflow中dataset.shuffle和dataset.batch dataset.repeat注意点
2020/06/08 Python
python中使用.py配置文件的方法详解
2020/11/23 Python
英国定做窗帘和纺织品面料一站式商店:Dekoria
2018/08/29 全球购物
Herschel Supply Co.美国:背包、手提袋及配件
2020/11/24 全球购物
芭比波朗加拿大官方网站:Bobbi Brown Cosmetics CA
2020/11/05 全球购物
大专计算机个人求职的自我评价
2013/10/21 职场文书
大一期末自我鉴定
2013/12/13 职场文书
学校介绍信范文
2014/01/14 职场文书
运动会邀请函范文
2014/02/06 职场文书
党的群众路线教育实践活动总结大会主持词
2014/10/30 职场文书
深入理解Vue的数据响应式
2021/05/15 Vue.js
使用Oracle跟踪文件的问题详解
2021/06/28 Oracle
win10以太网连接不上怎么办?Win10连接以太网详细教程
2022/04/08 数码科技