本文是一篇翻译文章。
原文链接: https://dev.to/hellomeghna/tips-to-write-better-conditionals-in-javascript-2189
什么是条件语句
不管是什么编程语言,代码中都少不了做判断,以及依据一个输入的条件决定要执行不同的操作。
例如,在一款游戏中,如果玩家的生命数小于0,游戏就宣告结束。在一款天气预报app中,在早上显示太阳的图片,在夜间显示星星和月亮的图片。在本文中我们就将介绍如何在JavaScript中进行类似的条件处理。
当你写JavaScript时,有时候会写出一大堆包含许多条件判断的代码。这些条件语句,在开始的时候可能还比较好理解,但是过一阵子之后就变得一团糟了。其实有比if/else
更棒的方式去实现条件判断。
这里就有一些关于如何写出干净优雅的条件判断的建议。
目录
- Array.includes
- 尽早退出和返回
- 用Object遍历 或者 Map 取代 Switch 表达式
- 使用默认参数 和 解构赋值
- 使用Array.every 和 Array.some去实现所有 和 部分条件判断
- Use Optional Chaining and Nullish Coalescing
1.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
上面的代码似乎看起来还行,那是因为我们只需要检测两种小动物。然而我们并不确定用户会输入什么。如果动物的类型变多了呢?如果我们继续通过扩展||
条件判断来满足需求,我们的代码会变得越来越难维护,并且看起来乱乱的。
解决方案:
我们可以用 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
这里我们创建了一个数组存放动物,这样判断条件就可以和剩余的代码隔离开了。现在,如果我们想继续扩充条件,我们只需要往数组里添加新的元素就可以了。(清晰多了呢)
2.尽早退出和返回
这是一个非常酷的小技巧,可以使你的代码看起来简洁。我记得我从工作的第一天起,我就被教导,在条件判断时要early exit
(尽早退出)。
让我们为上一个示例多添加些条件。如果animal 不再是一个简单的string
了,而是一个有特定属性的object
。
所以现在需求变成了下面这样:
- 如果没有animal,抛出一个错误
- 打印出animal的类型
- 打印出animal的名字
- 打印出animal的类型
const printAnimalDetails = animal => {
let result; // declare a variable to store the final value
// condition 1: check if animal has a value
if (animal) {
// condition 2: check if animal has a type property
if (animal.type) {
// condition 3: check if animal has a name property
if (animal.name) {
// condition 4: check if animal has a gender property
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'
对于上面的代码,你怎么看呢?
上面的代码没什么bug,但是看起来太长了,而且很难维护呢。一个新人可能得花个一上午来找哪些括号是一对的呢(手动滑稽)。如果逻辑再复杂点呢,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';
// Now in this line of code, we're sure that we have an animal with all //the three properties here.
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'
在重构版中,也用到了对象的解构赋值和函数参数的默认值。默认值的作用是,如果我们没有传参(undifined),也能保证不会报错。
另一个例子:
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
// condition 1: vegetable should be present
if (vegetable) {
// condition 2: must be one of the item from the list
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// condition 3: must be large quantity
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
接下来我要介绍我们的套路了—— 当遇到不可用的条件时尽早退出函数
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
// condition 1: throw error early
if (!vegetable) throw new Error('No vegetable from the list!');
// condition 2: must be in the list
if (vegetables.includes(vegetable)) {
console.log(`I like ${vegetable}`);
// condition 3: must be a large quantity
if (quantity >= 10) {
console.log('I have bought a large quantity');
}
}
}
这样重构后,我们就少了一层if
嵌套,当你的条件判断比较长时,这种代码风格尤为好用。
我们能进一步减少if
嵌套,通过对条件进行取反,然后return
。下面就是具体实现:
function printVegetablesWithQuantity(vegetable, quantity) {
const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];
if (!vegetable) throw new Error('No vegetable from the list!');
// condition 1: throw error early
if (!vegetables.includes(vegetable)) return;
// condition 2: return from the function is the vegetable is not in
// the list
console.log(`I like ${vegetable}`);
// condition 3: must be a large quantity
if (quantity >= 10) {
console.log('I have bought a large quantity');
}
}
通过对第二个条件取反,代码里再也看不到if
的嵌套了。这种技巧适用于当我们有好多条件判断,并且当满足某一个时,不再进行剩余的逻辑处理。
因此,我们的目标是消灭嵌套,及早return。但是return大法好,也不能"贪杯"啊~
3. 用Object遍历 或者 Map 取代 Switch 表达式
让我们看下这个例子,我们想基于颜色打印出水果:
function printFruits(color) {
// use switch case to find fruits by color
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']
上面的代码没什么错误,就是看起来有点长。我们可以用Object来实现同样的效果:
// use object literal to find fruits by color
const fruitColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum']
};
function printFruits(color) {
return fruitColor[color] || [];
}
当然也可以用Map
// use Map to find fruits by color
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 (Es6)的语法,大家注意兼容性呀!
也能用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);
}
4.使用默认参数 和 解构赋值
我们写JavaScript时,经常需要去检查null/undefined
,并对参数赋默认值,否则就会报错。
function printVegetablesWithQuantity(vegetable, quantity = 1) {
// if quantity has no value, assign 1
if (!vegetable) return;
console.log(`We have ${quantity} ${vegetable}!`);
}
//results
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,否则打印unknow
。
我们可以用默认值 和 解构赋值代替if (vegetable && vegetable.name) {}
。
// destructing - get name property only
// assign default empty object {}
function printVegetableName({name} = {}) {
console.log (name || 'unknown');
}
printVegetableName(undefined); // unknown
printVegetableName({ }); // unknown
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage
因为我们只需要name
属性,我们可以{name}
将它解构出来,然后我们就可以使用name
变量了,这样就不需要使用vegetable.name
了。
我们也给函数的参数设置了一个默认值{}
,否则当我们执行printVegeTable(undefined)
的时就会报错Cannot destructure property name of undefined or null
,因为undefined
不是对象,是不能解构的。
5.使用Array.every 和 Array.some去实现所有 和 部分条件判断
我么可以使用数组的这些方法来减少代码行数。下面的代码中我们想判断是否所有的水果都是红色。
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
let isAllRed = true;
// condition: all fruits must be red
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() {
// condition: short way, all fruits must be red
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() {
// condition: if any fruit is red
const isAnyRed = fruits.some(f => f.color == 'red');
console.log(isAnyRed); // true
}
6. Use Optional Chaining and Nullish Coalescing
这两个功能在JavaScript中是非常有用的。但是目前支持力度还不是很好,所以需要使用babel进行编译。
Optional chaining
使我们可以跳过中间层级去检查一个树状解构是否包含某个属性。nullish coalescing
可以和
Optional chaining
配合使用,来为变量赋默认值。
下面是个例子:
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) // 'Fiesta'
console.log(street) // 'Some Street Name'
console.log(phoneNumber) // undefined
所以如果我们想一辆汽车的制造商是否是美国,我们必须这么写代码:
const isManufacturerFromUSA = () => {
if(car && car.manufacturer && car.manufacturer.address &&
car.manufacturer.address.state === 'USA') {
console.log('true');
}
}
checkCarManufacturerState() // 'true'
你能看到,这么写代码是多么的凌乱。早已经有一些第三方库,像lodash
或者idx
有自己的函数,去简化这个操作。例如lodash
的_.get
。然而,如果JavaScript能原生支持这种操作就更好了。
下面就是一个例子:
// 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 isManufacturerFromUSA = () => {
if(car?.manufacturer?.address?.state === 'USA') {
console.log('true');
}
}
这个代码看起来漂亮多了,而且易于维护。这个特性已经在 TC39 stage 3
提案中了。我们再等等就可以用上了。
总结
让我们试着使用这些建议来写一些干净易于维护的代码吧,因为那些冗长的条件判断,再过几个月之后连你自己都看不懂了。
网友评论