-
Notifications
You must be signed in to change notification settings - Fork 3
Description
JS 代码简洁之道 - 条件语句
参考文章:
前言
在用 JavaScript 工作时,我们经常和条件语句打交道,这里有5条让你写出更好/干净的条件语句的建议。
- 多重判断时使用
Array.includes
- 更少的嵌套,尽早 return
- 使用默认参数和解构
- 倾向于遍历对象而不是 Switch 语句
- 对 "所有/部分" 判断使用
Array.every & Array.some
Array.includes
function test(fruit) {
// 如果 fruit 是 apple 或 strawberry,则打印 red
if (fruit == 'apple' || fruit == 'strawberry') {
console.log('red');
}
}
缺点:扩展性不强。假如需要添加判断更多 red fruit,只能用更多的 ||
来拓展条件语句。
function test(fruit) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// Array.prototype.includes()
if (redFruits.includes(fruit)) {
console.log('red');
}
}
优点:利用了数组“动态、灵活”的特性,将判断条件从 if 中提取出来,方便调整。
Array.prototype.includes()
作用:判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
语法:arr.includes(valueToFind[, fromIndex])
参数:
- valueToFind
需要查找的元素值。
Note: 使用 includes()比较字符串和字符时是区分大小写。
- fromIndex (可选)
从 fromIndex 索引处开始查找 valueToFind。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0。
如果 fromIndex 大于等于数组的长度,则会返回 false,且该数组不会被搜索。
返回值:
返回一个布尔值 Boolean ,如果在数组中找到了(如果传入了 fromIndex ,表示在 fromIndex 指定的索引范围中找到了)则返回 true 。
includes() 方法有意设计为通用方法。它不要求this值是数组对象,所以它可以被用于其他类型的对象 (比如类数组对象)。
避免多层嵌套,若条件符合则尽早 return
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 条件 1: fruit 必须有值
if (fruit) {
// 条件 2: 必须是red的
if (redFruits.includes(fruit)) {
console.log('red');
// 条件 3: quantity大于10
if (quantity > 10) {
console.log('big quantity');
}
}
} else {
throw new Error('No fruit!');
}
}
缺点:嵌套语句过多,当你有很长的 if 语句时,你可能滚动到最底层才知道还有 else 语句。
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 尽早抛出错误
if (!fruit) throw new Error('No fruit!');
if (redFruits.includes(fruit)) {
console.log('red');
if (quantity > 10) {
console.log('big quantity');
}
}
}
优化方案:当发现无效语句时,尽早 return。可以用“倒置判断条件”进一步分离更多 if...else...
语句,如下:
function test(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
// 尽早抛出错误
if (!fruit) throw new Error('No fruit!');
// 当水果不是红色时停止继续执行(倒置判断)
if (!redFruits.includes(fruit)) return;
console.log('red');
if (quantity > 10) {
console.log('big quantity');
}
}
优化原则:在发现无效条件时,尽早 return。通过 “倒置判断条件 & 尽早return” 进一步减少if嵌套。
优点:少了一层嵌套语句,代码结构更加清晰。
应当尽力减少嵌套和尽早return,但不要过度。一两层的
if
语句嵌套在可接受范围之内,此时代码比较短且直接,包含if嵌套的更清晰,若无脑添加倒置判断条件可能加重思考的负担(增加认知载荷)。
使用默认参数和解构
在JavaScript中我们总是需要检查 null / undefined
的值和指定默认值。ES6 提供了函数参数的默认值写法,可以利用该特性对条件语句进行简化。
function test(fruit, quantity) {
if (!fruit) return;
// 如果 quantity 参数没有传入,设置默认值为 1
const q = quantity || 1;
console.log(`We have ${q} ${fruit}!`);
}
我们可以通过声明 "默认函数参数" 来消除变量 q:
function test(fruit, quantity = 1) {
// 如果 quantity 参数没有传入,设置默认值为 1
if (!fruit) return;
console.log(`We have ${quantity} ${fruit}!`);
}
当函数参数为 object 类型时,我们可以通过“对象解构”以及“函数默认参数”进行简化。
function test(fruit) {
// 当 fruit 值存在时打印 fruit 的 name 属性值,若不存在则打印 'unknown'
if (fruit && fruit.name) {
console.log (fruit.name);
} else {
console.log('unknown');
}
}
优化后:当 fruit 值不存在时,函数参数默认为 {}
。此时 name 属性值为 undefined
,对应布尔值为 false
。
// 解构 - 仅仅获取 name 属性
// 为其赋默认值为空对象
function test({name} = {}) {
console.log (name || 'unknown');
}
此处还有一个优化技巧:简单的条件语句利用
&&
,||
以及三元运算符来替代,参照name || 'unknown'
的写法。
“对象映射”代替“条件语句”
function test(color) {
// 使用条件语句来寻找对应颜色的水果
switch (color) {
case 'red':
return ['apple', 'strawberry'];
case 'yellow':
return ['banana', 'pineapple'];
case 'purple':
return ['grape', 'plum'];
default:
return [];
}
}
上述 switch 语句用对象遍历实现相同的结果,语法看起来更简洁:
// 判断条件的相关配置
const fruitColor = {
red: () => ['apple', 'strawberry'],
yellow: () => ['banana', 'pineapple'],
purple: () => ['grape', 'plum']
};
fruitColor[color]() // 执行语句
对象映射的优势:分离配置信息(判断条件)与执行动作。
我们可定义一个 object 作为配置对象,存放不同状态,并通过链表进行查找。
对象映射可以将条件配置与具体执行分离。如果要增加其他状态,只修改配置对象即可(可灵活调整)。
const statusMap = {
:()=>{
console.log('a1')
},
2:()=>{
console.log('b2')
}
/* n.... */
}
// 执行
let a = 1
statusMap[a || 1]()
object 对象的 key 值只能是 string 类型,因此我们也可以使用 Map 替换 object,Map 可接受多种类型的 key 实现相同的结果:
const fruitColor = new Map()
.set('red', () => ['apple', 'strawberry'])
.set('yellow', () => ['banana', 'pineapple'])
.set('purple', () => ['grape', 'plum']);
fruitColor[color]()
当然,有对象映射,对应也有数组映射,数组映射的 key 值为
0~n
的索引,具体使用依据业务场景进行选择。
对 "所有/部分" 判断使用 Array.prototype.every & Array.prototype.some
Array.prototype.every & Array.prototype.some
当判断条件为是否都满足 / 是否都不满足时,可以使用 Array.prototype.every
:
假设我们要检测所有水果是否都为红色,一般逻辑通过 for 循环依次判断来实现:
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
}
利用 JavaScript Array 的内置方法 Array.prototype.every()
,我们可以减少代码行数:
const fruits = [
{ name: 'apple', color: 'red' },
{ name: 'banana', color: 'yellow' },
{ name: 'grape', color: 'purple' }
];
function test() {
// Array.prototype.every():遍历数组并对元素执行回调,回调返回一个 boolean 值,当所有返回的 boolean 值均为 true 是,整体返回 true。
const isAllRed = fruits.every(f => f.color == 'red');
}
当判断条件为是否存在满足条件的情况 / 是否存在不满足条件时的情况,可以使用 Array.prototype.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');
}
总结
- 避免多层条件语句嵌套:尽早 return,倒置条件判断。
- 条件语句应避免“写死”:尽量用 “对象 / 数组 / Map” 等可动态调整的结构,即具有灵活性,又分离了逻辑条件与执行语句。
- 巧用
Array
数组结构的内置方法:Array.prototype.includes()
,Array.prototype.some()
,Array.prototype.every()
等。
2022.06.29 补充
多元条件判断
原始代码:
/**
* 按钮点击事件
* @param {number} status 活动状态:1开团进行中 2开团失败 3 开团成功 4 商品售罄 5 有库存未开团
* @param {string} identity 身份标识:guest客态 master主态
*/
const onButtonClick = (status,identity)=>{
if(identity == 'guest'){
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}else if(identity == 'master') {
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}
}
上述代码中,存在多层的条件判断嵌套关系
修改后的代码:
const actions = ()=>{
const functionA = ()=>{/*do sth*/}
const functionB = ()=>{/*do sth*/}
const functionC = ()=>{/*send log*/}
return new Map([
[/^guest_[1-4]$/,functionA],
[/^guest_5$/,functionB],
[/^guest_.*$/,functionC],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
action.forEach(([key,value])=>value.call(this))
}
特点:
利用了 Map 对象:
- 可接纳任何类型作为 key 值,此处利用了正则表达式
- 实现了多元嵌套条件的字符串拼接以及重复逻辑复用。
- 简单调用:利用数组循环的特性,符合正则条件的逻辑都会被执行,可以同时执行公共逻辑和单独逻辑。