「译」更好的 JavaScript 条件式和匹配条件的提示与技巧
介绍
如果像我一样喜欢看干净的代码,则将尝试使用尽可能少的条件语句来编写代码。通常,面向对象的编程使我们能够避免条件,而将其替换为多态性和继承。我相信我们应该尽可能地遵循这些原则。
就像我在另一篇文章 JavaScript Clean Code Best Practices 中提到的那样,您并不仅仅是为机器编写代码,而是为“未来的自己”和“其他人”编写代码。
另一方面,由于各种原因,我们可能最终在代码中使用条件。也许我们的修复期限很紧,或者不使用条件语句将对我们的代码库造成太大的更改,等等。本文旨在帮助您解决这些情况并帮助您组织条件语句。
技巧
以下是有关如何构造if...else语句以及如何减少编写以实现更多目标的技巧。享受吧!
1. 重要的事情先做,琐碎的但很重要
不要使用否定条件(它们可能会造成困惑),而请使用条件简写来表示 boolean 变量。我不能特别强调这一点,尤其是关于否定条件的部分。这是一种不自然的做事方式。
Bad
const isEmailNotVerified = (email) => {
// implementation
}
if (!isEmailNotVerified(email)) {
// do something...
}
if (isVerified === true) {
// do something...
}
Good
const isEmailVerified = (email) => {
// implementation
}
if (isEmailVerified(email)) {
// do something...
}
if (isVerified) {
// do something...
}
现在,当我们清楚以上几点之后,就可以开始了。
2.对于多个条件,请使用 Array.includes
假设我们要检查汽车模型是 renault 还是 peugeot 在我们的功能中。该代码可能看起来像这样:
const checkCarModel = (model) => {
if(model === 'renault' || model === 'peugeot') {
console.log('model valid');
}
}
checkCarModel('renault'); // outputs 'model valid'
考虑到我们只有两个模型,这样做看起来是可以接受的,但是如果我们要检查另一个模型怎么办?还是其中几个?如果我们添加更多的 or 语句,则代码将更难维护,而不是那么干净。为了使它更整洁,我们可以将函数重写为如下形式:
const checkCarModel = (model) => {
if(['peugeot', 'renault'].includes(model)) {
console.log('model valid');
}
}
checkCarModel('renault'); // outputs 'model valid'
上面的代码看起来已经更好了。为了使它更好,我们可以创建一个变量来保存汽车模型:
const checkCarModel = (model) => {
const models = ['peugeot', 'renault'];
if(models.includes(model)) {
console.log('model valid');
}
}
checkCarModel('renault'); // outputs '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')); // outputs false
如果您喜欢命令式的处理方式,则上面的代码可能会很好。另一方面,如果您不在乎背后发生了什么,则可以重写上面的函数并使用 Array.every 或 Array.find 获得相同的结果。
const checkEveryModel = (model) => {
return cars.every(car => car.model === model);
}
console.log(checkEveryModel('renault')); // outputs false
通过使用 Array.find,稍作调整就可以达到相同的结果,并且性能应该是相同的,因为两个函数都对数组中的每个元素执行回调,并且如果发现 falsy,则立即返回 false。
const checkEveryModel = (model) => {
return cars.find(car => car.model !== model) === undefined;
}
console.log(checkEveryModel('renault')); // outputs 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')); // outputs true
5.尽早返回而不是if...else分支
当我还是一个学生的时候,我被教导说一个函数应该只有一个 return 语句,并且只能从一个位置返回。如果谨慎处理,这不是一个坏方法,这意味着我们应该认识到会导致条件嵌套地狱的情况。if...else 如果失去控制,多个分支和嵌套可能会很痛苦。
另一方面,如果代码库很大,并且包含很多行,那么在深处某处的 return 语句将是一个问题。如今,我们将关注点与 SOLID 原则分离开来,因此,大量的代码行应该很少见。
让我们创建一个示例来说明这一点,并说我们要显示给定汽车的型号和制造年份。
const checkModel = (car) => {
let result; // first, we need to define a result value
// check if car exists
if(car) {
// check if car model exists
if (car.model) {
// check if car year exists
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; // our single return statement
}
console.log(checkModel()); // outputs 'No car'
console.log(checkModel({ year: 1988 })); // outputs 'No car model'
console.log(checkModel({ model: 'ford' })); // outputs 'No car year'
console.log(checkModel({ model: 'ford', year: 1988 })); // outputs 'Car model: ford; Manufacturing year: 1988;'
如您所见,即使对于我们这个简单的问题,上面的代码也相当长。想象一下,如果我们拥有更复杂的逻辑,将会发生什么。很多 if...else 陈述。
我们可以通过更多步骤来重构上面的功能,从而使每个功能都更好。例如,使用三元运算符,包含&&条件等,但是我将跳到最后,向您展示如何使用现代 JavaScript 功能和多个 return 语句将其简化。
const checkModel = ({model, year} = {}) => {
if(!model && !year) return 'No car';
if(!model) return 'No car model';
if(!year) return 'No car year';
// here we are free to do whatever we want with the model or year
// we made sure that they exist
// no more checks required
// doSomething(model);
// doSomethingElse(year);
return `Car model: ${model}; Manufacturing year: ${year};`;
}
console.log(checkModel()); // outputs 'No car'
console.log(checkModel({ year: 1988 })); // outputs 'No car model'
console.log(checkModel({ model: 'ford' })); // outputs 'No car year'
console.log(checkModel({ model: 'ford', year: 1988 })); // outputs '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()); // outputs []
console.log(getCarsByState('usa')); // outputs ['Ford', 'Dodge']
console.log(getCarsByState('italy')); // outputs ['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()); // outputs []
console.log(getCarsByState('usa')); //outputs ['Ford', 'Dodge']
console.log(getCarsByState('italy')); // outputs ['Fiat']
或者,我们可以为每个州创建一个带有可用汽车列表的类,并在需要时使用它,但这是另一篇文章的主题。这篇文章是关于条件的。更合适的更改是使用对象文字。
const carState = {
usa: ['Ford', 'Dodge'],
france: ['Renault', 'Peugeot'],
italy: ['Fiat']
};
const getCarsByState = (state) => {
return carState[state] || [];
}
console.log(getCarsByState()); // outputs []
console.log(getCarsByState('usa')); // outputs ['Ford', 'Dodge']
console.log(getCarsByState('france')); // outputs ['Renault', 'Peugeot']
7.使用可选链和无效合并
我可以从说“最后”开始。我认为,这两个功能是对 JavaScript 语言的非常有用的补充。作为一个来自 C# 世界的人,我可以说我经常使用它们。
在撰写本文时,还没有完全支持这些选项,因此您需要使用Babel编译以这种方式编写的代码。您可以在此处检查可选链和在此处进行无效合并。
可选链接使我们能够处理树状结构,而无需显式检查中间节点是否存在,并且空值合并与可选链接结合使用非常有效,它可用于确保不存在的默认值。
让我们用一些示例来验证上面的语句,然后从老的处理方式开始。
const car = {
model: 'Fiesta',
manufacturer: {
name: 'Ford',
address: {
street: 'Some Street Name',
number: '5555',
state: 'USA'
}
}
}
// to get the car model
const model = car && car.model || 'default model';
// to get the manufacturer street
const street = car && car.manufacturer && car.manufacturer.address && car.manufacturer.address.street || 'default street';
// request an un-existing property
const phoneNumber = car && car.manufacturer && car.manufacturer.address && car.manufacturer.phoneNumber;
console.log(model) // outputs 'Fiesta'
console.log(street) // outputs 'Some Street Name'
console.log(phoneNumber) // outputs undefined
因此,如果我们想打印出这家汽车制造商来自美国,那么代码将如下所示:
const checkCarManufacturerState = () => {
if(car && car.manufacturer && car.manufacturer.address && car.manufacturer.address.state === 'USA') {
console.log('Is from USA');
}
}
checkCarManufacturerState() // outputs 'Is from USA'
我不需要告诉您在对象结构更复杂的情况下这会变得多么混乱。例如,诸如 lodash 的许多库都有其自己的变通办法,但我们不希望那样,我们希望能够在原生 js中实现。让我们看看一种新的做事方式。
// to get the car model
const model = car?.model ?? 'default model';
// to get the manufacturer street
const street = car?.manufacturer?.address?.street ?? 'default street';
// to check if the car manufacturer is from the 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;