分享 JavaScript 条件语句的 6 个技巧
什么是条件语句?
在任何编程语言中,代码都依赖输入的给定条件来做决策并执行动作的。
例如,在游戏中,如果玩家的生命值为 0,那么游戏就会结束。在天气 APP 中,如果是早上查看,就展示的日出的图片;晚上则展示星星和月亮。在这篇文章中,我们会探讨在 JavaScript 中这些所谓的条件语句是如何工作的。
如果你使用JavaScript,你将会写下大量拥有巨多涉及条件的代码。条件或许在你一开始学习时很简单,但是这不仅仅是写几个 if/else
语句。这有一些有用的提示,来帮你写出更好、更整洁的条件代码。
一、 Array.includes
针对多个条件使用 Array.includes
例如:
function printAnimals(animal) {
if (animal === 'dog' || animal === 'cat') {
console.log(`I have a ${animal}`);
}
}
console.log(printAnimals('dog')); // I have a dog
当我们只有两个动物要检查时,上面的代码看上去还不错。然而,我们不确定用户的输入。如果,我们还有其他动物呢?如果我们继续使用 OR
来扩展这个语句,代码将变得难以维护,并且一点儿都整洁了。
解决方案:
我们可以使用 Array.includes
来重写上面的条件
function printAnimals(animal) {
const animals = ['dog', 'cat', 'hamster', 'turtle'];
if (animals.includes(animal)) {
console.log(`I have a ${animal}`);
}
}
console.log(printAnimals('hamster')); // I have a hamster
到这儿,我们已经创建了一个动物数组,以便条件可以从代码的其余部分分别提取。现在,如果我们需要检查任意其他动物,就只需要添加新的数组项。
我们还使用在函数的范围外的变量 animals
,并在代码的任意位置重用它。这个方式写出的代码更简洁、更好懂、更易于维护,不是吗?
2. 提前退出 / 提前返回
这是一个非常酷的技巧,可以凝练你的代码并使它看起来更简洁。我记得当我开始专业地工作时,第一天就学会了编写提前退出的条件语句。
让我们以前面的示例为例,并添加多一些条件。如果 animal 不是一个简单的字符串,而是一个具有特定属性的对象该怎么办?
所以现在的要求是:
- 如果没有 animal,请抛出错误
- 打印 animal 的 type 值
- 打印 animal 的 name 值
- 打印 animal 的 gender 值
const printAnimalDetails = animal => {
let result; // 声明一个变量来存储最终值
// 条件 1: 检查 animal 是否具有值
if (animal) {
// 条件 2: 检查 animal 是否具有 type 属性
if (animal.type) {
// 条件 3: 检查 animal 是否具有 name 属性
if (animal.name) {
// 条件 4: 检查 animal 是否具有 gender 属性
if (animal.gender) {
result = `${animal.name} is a ${animal.gender} ${animal.type};`;
} else {
result = "No animal gender";
}
} else {
result = "No animal name";
}
} else {
result = "No animal type";
}
} else {
result = "No animal";
}
return result;
};
console.log(printAnimalDetails()); // 'No animal'
console.log(printAnimalDetails({ type: "dog", gender: "female" })); // 'No animal name'
console.log(printAnimalDetails({ type: "dog", name: "Lucy" })); // 'No animal gender'
console.log(
printAnimalDetails({ type: "dog", name: "Lucy", gender: "female" })
); // 'Lucy is a female dog'
你觉得上面的代码如何?
代码很好地运行,但是这样的代码又长又难以维护。如果不使用标记,你可能会浪费一些时间来弄清楚括号的位置。😄 想象一下如果代码有更复杂的逻辑会发生什么。大量的 if..else
语句!
我们可以使用三元运算符、&&
条件等来重构上述函数。但我们可以使用多个 return 语句编写更精确的代码。
const printAnimalDetails = ({type, name, gender } = {}) => {
if(!type) return 'No animal type';
if(!name) return 'No animal name';
if(!gender) return 'No animal gender';
// 现在,在这行代码中,我们确定这里有一个具有三个属性的 animal。
return `${name} is a ${gender} ${type}`;
}
console.log(printAnimalDetails()); // 'No animal type'
console.log(printAnimalDetails({ type: dog })); // 'No animal name'
console.log(printAnimalDetails({ type: dog, gender: female })); // 'No animal name'
console.log(printAnimalDetails({ type: dog, name: 'Lucy', gender: 'female' })); // 'Lucy is a female dog'
在重构版本中,还包括了 destructuring
和 default parameters
。默认参数确保了,如果我们将 undefined
作为方法的参数传递,我们仍会有一个能被解构的值,这里是一个空对象 {}
。
通常,在专业领域中,代码是在这两种方法之间的某个位置编写的。
另一个例子:
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
// 条件 1: 应该有 vegetable
if (vegetable) {
// 条件 2: 必须是列表中的一项
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// 条件 3: 必须要大量
if (quantity >= 10) {
console.log('I have bought a large quantity');
}
}
} else {
throw new Error('No vegetable from the list!');
}
}
printVegetablesWithQuantity(null); // No vegetable from the list!
printVegetablesWithQuantity('cabbage'); // I like cabbage
printVegetablesWithQuantity('cabbage', 20);
// 'I like cabbage`
// 'I have bought a large quantity'
现在,我们有:
- 1 条用于过滤无效条件的 if/else 语句
- 3 级嵌套的 if 语句(条件 1、2 和 3)
遵循的一般规则是 发现无效条件时提前返回
。
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
// 条件 1: 提前抛出错误
if (!vegetable) throw new Error('No vegetable from the list!');
// 条件 2: 必须在列表中
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// 条件 3: 必须要大量
if (quantity >= 10) {
console.log('I have bought a large quantity');
}
}
}
这样,我们就可以少一层嵌套的语句。这种编码风格非常好,特别是当你有很长的 if 语句时。
我们可以通过反转条件并提前返回来进一步减少 if
的嵌套。查看下面的条件 2,了解我们是如何做到这一点:
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
if (!vegetable) throw new Error('No vegetable from the list!');
// 条件 1: 提前抛出错误
if (!vegetables.includes(vegetable)) return;
// 条件 2: vegetable 不存在时,函数提前返回
// the list
console.log(`I like ${vegetable}`);
// 条件 3: 必须要大量
if (quantity >= 10) {
console.log('I have bought a large quantity');
}
}
通过条件 2 的反转,代码不再具有嵌套语句。当我们有很多条件、不满足任何特定条件,并且想要停止进一步的过程时,这个技巧是非常有用的。
因此,请始终以 更少嵌套
为目标,并 提前返回
,但不要过度使用它。
三、字面量对象或 Map 替代 Switch 语句
让我们先看看下面的例子,我们想根据颜色打印水果:
function printFruits(color) {
// 使用 switch case 通过颜色找水果
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
printFruits(null); // []
printFruits('yellow'); // ['banana', 'pineapple']
上面的代码并没有错,但是它比较冗长。我们可以用 字面量对象
这更简洁的语法来实现的结果:
// 使用字面量对象通过颜色找水果
const fruitColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum']
};
function printFruits(color) {
return fruitColor[color] || [];
}
亦或,你可以使用 Map
构建获得相同的结果:
// 使用 Map 通过颜色找水果
const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);
function printFruits(color) {
return fruitColor.get(color) || [];
}
Map
从 ES2015 起支持的对象属性,它允许你存储 键值对
。
上面的例子,我们也可以使用 Array.filter
来得到相同的结果。
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'strawberry', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'pineapple', color: 'yellow' },
{ name: 'grape', color: 'purple' },
{ name: 'plum', color: 'purple' }
];
function printFruits(color) {
return fruits.filter(fruit => fruit.color === color);
}
四、默认参数和解构
当使用 JavaScript 时,我们经常需要检查 null/undefined
值和赋 默认值
,否则编译就会中断。
function printVegetablesWithQuantity(vegetable, quantity = 1) {
// 如果 quantity 没有传值,赋值 1
if (!vegetable) return;
console.log(`We have ${quantity} ${vegetable}!`);
}
// 结果
printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!
printVegetablesWithQuantity('potato', 2); // We have 2 potato!
如果 vegetable
是一个对象呢?我们能赋默认值吗?
function printVegetableName(vegetable) {
if (vegetable && vegetable.name) {
console.log (vegetable.name);
} else {
console.log('unknown');
}
}
printVegetableName(undefined); // unknown
printVegetableName({}); // unknown
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage
在上面的例子中,我们想打印 vegetable 的 name (如果可用)或打印unknown
。
我们可以使用默认参数和解构来避免使用条件语句 if (vegetable && vegetable.name) {}
。
// 解构 - 进获取 name 属性
// 赋默认空对象 {}
function printVegetableName({name} = {}) {
console.log (name || 'unknown');
}
printVegetableName(undefined); // unknown
printVegetableName({ }); // unknown
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage
由于我们仅仅需要 name
属性,可以使用 { name }
来解构参数,然后我们就可以在代码中使用 name
作为变量来代替 vegetable.name
。
我们也把空对象 {} 赋值为默认值,否则,在执行 printVegetableName(undefined)
这行代码时会报错 - Cannot destructure property name of undefined or null
,因为 undefined
中没有属性 name。
5. 使用 Array.every 和 Array.some 匹配所有或部分条件
我们可以通过使用这些数组方法减少代码行数。查看下面的代码,我们想要检查是否所有水果的颜色都是红色:
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
let isAllRed = true;
// 条件:所有水果都是红色
for (let f of fruits) {
if (!isAllRed) break;
isAllRed = (f.color == 'red');
}
console.log(isAllRed); // false
}
上面的代码太长了!我们可以使用 Array.every
减少行数:
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// 条件(短方法):所有水果都是红色
const isAllRed = fruits.every(f => f.color == 'red');
console.log(isAllRed); // false
}
同样,如果我们想要测试是否存在红色的水果,我们可以在一行中使用 Array.some
实现。
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// 条件:如果存在红色的水果
const isAnyRed = fruits.some(f => f.color == 'red');
console.log(isAnyRed); // true
}
六、使用可选链和空值合并
这两个功能对于 使用 JavaScript 编写更简洁的条件语句非常有用。我写这该文时尚未得到完全支持,因此你需要使用 Babel 来编译代码。
可选链
允许我们在不显式检查中间节点是否存在的情况下处理树状解构,并且 空值合并
与可选链合并使用可以很好的确保不存在节点的默认值。
这儿还有例子:
const car = {
model: 'Fiesta',
manufacturer: {
name: 'Ford',
address: {
street: 'Some Street Name',
number: '5555',
state: 'USA'
}
}
}
// 获取 car model
const model = car && car.model || 'default model';
// 得到 manufacturer street
const street = car && car.manufacturer && car.manufacturer.address &&
car.manufacturer.address.street || 'default street';
// 请求一个不存在的 property
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 制造的 car,代码就会像这样:
const isManufacturerFromUSA = () => {
if(car && car.manufacturer && car.manufacturer.address &&
car.manufacturer.address.state === 'USA') {
console.log('true');
}
}
checkCarManufacturerState() // 'true'
你可以清楚的看到在对象解构变得复杂后这是如何的混乱。有一些第三方库,如 lodash
或 idx
,他们都有自己的函数。例如 lodash 有 _.get
方法。然而,使用 JavaScript 自身的功能酷毙了。
新功能的工作方式:
// 获取 car 模型
const model = car?.model ?? 'default model';
// 获取 manufacturer 街道
const street = car?.manufacturer?.address?.street ?? 'default street';
// 检查 car 的 manufacturer 是否为 USA
const isManufacturerFromUSA = () => {
if(car?.manufacturer?.address?.state === 'USA') {
console.log('true');
}
}
这就看上去漂亮极了,也更易于维护。它已经在 TC39 stage 3
,让我们等它成获取批准,然后我们就可以在任何地方看到这种不可思议的语法。
总结
让我们学习并尝试新的技巧和技术,来编写更整洁、可维护的代码。因为经过几个月,这些长长的条件语句就像射在自己脚上一样。😄
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: