作者:cuppar
/article/tips-to-write-better-conditionals-in-javascript-dev-community
在任何编程语言中,代码需要根据不同的条件在给定的输入中做不同的决定和执行相应的动作。
例如,在一个游戏中,如果玩家生命点为0,游戏结束。在天气应用中,如果在早上被查看,显示一个日出图片,如果是晚上,则显示星星和月亮。在这篇文章中,我们将探索JavaScript中所谓的条件语句如何工作。
如果你使用JavaScript工作,你将写很多包含条件调用的代码。条件调用可能初学很简单,但是还有比写一对对if/else更多的东西。这里有些编写更好更清晰的条件代码的有用提示。
数组方法 Array.includes
提前退出 / 提前返回
用对象字面量或Map替代Switch语句
默认参数和解构
用 Array.every & Array.some 匹配全部/部分内容
使用可选链和空值合并
1. 数组方法 Array.includes
使用Array.includes
进行多条件选择
例如:
functionprintAnimals(animal){if(animal==='dog'||animal==='cat'){console.log(Ihavea${animal});}}console.log(printAnimals('dog'));//Ihaveadog
上面的代码看起来很好因为我们只检查了两个动物。然而,我们不确定用户输入。如果我们要检查任何其他动物呢?如果我们通过添加更多“或”语句来扩展,代码将变得难以维护和不清晰。
解决方案:
我们可以通过使用Array.includes
来重写上面的条件
functionprintAnimals(animal){constanimals=['dog','cat','hamster','turtle'];if(animals.includes(animal)){console.log(Ihavea${animal});}}console.log(printAnimals('hamster'));//Ihaveahamster
这里,我们创建来一个动物数组,所以条件语句可以和代码的其余部分抽象分离出来。现在,如果我们想要检查任何其他动物,我们只需要添加一个新的数组项。
我们也能在这个函数作用域外部使用这个动物数组变量来在代码中的其他任意地方重用它。这是一个编写更清晰、易理解和维护的代码的方法,不是吗?
2. 提前退出 / 提前返回
这是一个精简你的代码的非常酷的技巧。我记得当我开始专业工作时,我在第一天学习使用提前退出来编写条件。
让我们在之前的例子上添加更多的条件。用包含确定属性的对象替代简单字符串的动物。
现在的需求是:
如果没有动物,抛出一个异常
打印动物类型
打印动物名字
打印动物性别
constprintAnimalDetails=animal=>{letresult;//declareavariabletostorethefinalvalue//condition1:checkifanimalhasavalueif(animal){//condition2:checkifanimalhasatypepropertyif(animal.type){//condition3:checkifanimalhasanamepropertyif(animal.name){//condition4:checkifanimalhasagenderpropertyif(animal.gender){result=${animal.name}isa${animal.gender}${animal.type};;}else{result="Noanimalgender";}}else{result="Noanimalname";}}else{result="Noanimaltype";}}else{result="Noanimal";}returnresult;};console.log(printAnimalDetails());//'Noanimal'console.log(printAnimalDetails({type:"dog",gender:"female"}));//'Noanimalname'console.log(printAnimalDetails({type:"dog",name:"Lucy"}));//'Noanimalgender'console.log(printAnimalDetails({type:"dog",name:"Lucy",gender:"female"}));//'Lucyisafemaledog'
你觉得上面的代码怎么样?
它工作得很好,但是代码很长并且维护困难。如果不使用lint工具,找出闭合花括号在哪都会浪费很多时间。???? 想象如果代码有更复杂的逻辑会怎么样?大量的if..else语句。
我们能用三元运算符、&&条件等语法重构上面的功能,但让我们用多个返回语句编写更清晰的代码。
constprintAnimalDetails=({type,name,gender}={})=>{if(!type)return'Noanimaltype';if(!name)return'Noanimalname';if(!gender)return'Noanimalgender';//Nowinthislineofcode,we'resurethatwehaveananimalwithall//thethreepropertieshere.return${name}isa${gender}${type};}console.log(printAnimalDetails());//'Noanimaltype'console.log(printAnimalDetails({type:dog}));//'Noanimalname'console.log(printAnimalDetails({type:dog,gender:female}));//'Noanimalname'console.log(printAnimalDetails({type:dog,name:'Lucy',gender:'female'}));//'Lucyisafemaledog'
在这个重构过的版本中,也包含了解构和默认参数。默认参数确保如果我们传递undefined作为一个方法的参数,我们仍然有值可以解构,在这里它是一个空对象{}。
通常,在专业领域,代码被写在这两种方法之间。
另一个例子:
functionprintVegetablesWithQuantity(vegetable,quantity){constvegetables=['potato','cabbage','cauliflower','asparagus'];//condition1:vegetableshouldbepresentif(vegetable){//condition2:mustbeoneoftheitemfromthelistif(vegetables.includes(vegetable)){console.log(Ilike${vegetable});//condition3:mustbelargequantityif(quantity>=10){console.log('Ihaveboughtalargequantity');}}}else{thrownewError('Novegetablefromthelist!');}}printVegetablesWithQuantity(null);//Novegetablefromthelist!printVegetablesWithQuantity('cabbage');//IlikecabbageprintVegetablesWithQuantity('cabbage',20);//'Ilikecabbage//'Ihaveboughtalargequantity'
现在,我们有:
1 if/else 语句过滤非法条件
3 级嵌套if语句 (条件 1, 2, & 3)
一个普遍遵循的规则是:在非法条件匹配时提前退出。
functionprintVegetablesWithQuantity(vegetable,quantity){constvegetables=['potato','cabbage','cauliflower','asparagus'];//condition1:throwerrorearlyif(!vegetable)thrownewError('Novegetablefromthelist!');//condition2:mustbeinthelistif(vegetables.includes(vegetable)){console.log(Ilike${vegetable});//condition3:mustbealargequantityif(quantity>=10){console.log('Ihaveboughtalargequantity');}}}
通过这么做,我们少了一个嵌套层级。当你有一个长的if语句时,这种代码风格特别好。
我们能通过条件倒置和提前返回,进一步减少嵌套的if语句。查看下面的条件2,观察我们是怎么做的
functionprintVegetablesWithQuantity(vegetable,quantity){constvegetables=['potato','cabbage','cauliflower','asparagus'];if(!vegetable)thrownewError('Novegetablefromthelist!');//condition1:throwerrorearlyif(!vegetables.includes(vegetable))return;//condition2:returnfromthefunctionisthevegetableisnotin//thelistconsole.log(Ilike${vegetable});//condition3:mustbealargequantityif(quantity>=10){console.log('Ihaveboughtalargequantity');}}
通过倒置条件2,代码没有嵌套语句了。这种技术在我们有很多条件并且当任何特定条件不匹配时,我们想停止进一步处理的时候特别有用。
所以,总是关注更少的嵌套和提前返回,但也不要过度地使用。
3. 用对象字面量或Map替代Switch语句
让我们来看看下面的例子,我们想要基于颜色打印水果:
functionprintFruits(color){//useswitchcasetofindfruitsbycolorswitch(color){case'red':return['apple','strawberry'];case'yellow':return['banana','pineapple'];case'purple':return['grape','plum'];default:return[];}}printFruits(null);//[]printFruits('yellow');//['banana','pineapple']
上面的代码没有错误,但是它仍然有些冗长。相同的功能能用对象字面量以更清晰的语法实现:
//useobjectliteraltofindfruitsbycolorconstfruitColor={red:['apple','strawberry'],yellow:['banana','pineapple'],purple:['grape','plum']};functionprintFruits(color){returnfruitColor[color]||[];}
另外,你也能用Map
来实现相同的功能:
//useMaptofindfruitsbycolorconstfruitColor=newMap().set('red',['apple','strawberry']).set('yellow',['banana','pineapple']).set('purple',['grape','plum']);functionprintFruits(color){returnfruitColor.get(color)||[];}
Map
允许保存键值对,是自从ES以来可以使用的对象类型。
对于上面的例子,相同的功能也能用数组方法Array.filter
来实现。
constfruits=[{name:'apple',color:'red'},{name:'strawberry',color:'red'},{name:'banana',color:'yellow'},{name:'pineapple',color:'yellow'},{name:'grape',color:'purple'},{name:'plum',color:'purple'}];functionprintFruits(color){returnfruits.filter(fruit=>fruit.color===color);}
4. 默认参数和解构
当使用 JavaScript 工作时,我们总是需要检查null/undefined
值并赋默认值,否则可能编译失败。
functionprintVegetablesWithQuantity(vegetable,quantity=1){//ifquantityhasnovalue,assign1if(!vegetable)return;console.log(Wehave${quantity}${vegetable}!);}//resultsprintVegetablesWithQuantity('cabbage');//Wehave1cabbage!printVegetablesWithQuantity('potato',2);//Wehave2potato!
如果 vegetable 是一个对象呢?我们能赋一个默认参数吗?
functionprintVegetableName(vegetable){if(vegetable&&vegetable.name){console.log(vegetable.name);}else{console.log('unknown');}}printVegetableName(undefined);//unknownprintVegetableName({});//unknownprintVegetableName({name:'cabbage',quantity:2});//cabbage
在上面的例子中,如果vegetable 存在,我们想要打印 vegetable name, 否则打印"unknown"。
我们能通过使用默认参数和解构来避免条件语句 if (vegetable && vegetable.name) {} 。
//destructing-getnamepropertyonly//assigndefaultemptyobject{}functionprintVegetableName({name}={}){console.log(name||'unknown');}printVegetableName(undefined);//unknownprintVegetableName({});//unknownprintVegetableName({name:'cabbage',quantity:2});//cabbage
因为我们只需要name
属性,所以我们可以使用{ name }
解构参数,然后我们就能在代码中使用name
作为变量,而不是vegetable.name
。
我们还赋了一个空对象 {} 作为默认值,因为当执行printVegetableName(undefined)
时会得到一个错误:不能从undefined
或null
解构属性name
,因为在undefined
中没有name
属性。
5. 用 Array.every & Array.some 匹配全部/部分内容
我们能使用数组方法减少代码行。查看下面的代码,我们想要检查是否所有的水果都是红色的:
constfruits=[{name:'apple',color:'red'},{name:'banana',color:'yellow'},{name:'grape',color:'purple'}];functiontest(){letisAllRed=true;//condition:allfruitsmustberedfor(letfoffruits){if(!isAllRed)break;isAllRed=(f.color=='red');}console.log(isAllRed);//false}
这代码太长了!我们能用Array.every
来减少代码行数:
constfruits=[{name:'apple',color:'red'},{name:'banana',color:'yellow'},{name:'grape',color:'purple'}];functiontest(){//condition:shortway,allfruitsmustberedconstisAllRed=fruits.every(f=>f.color=='red');console.log(isAllRed);//false}
相似地,如果我们想测试是否有任何红色的水果,我们能用一行Array.some
来实现它。
constfruits=[{name:'apple',color:'red'},{name:'banana',color:'yellow'},{name:'grape',color:'purple'}];functiontest(){//condition:ifanyfruitisredconstisAnyRed=fruits.some(f=>f.color=='red');console.log(isAnyRed);//true}
6. 使用可选链和空值合并
这有两个为编写更清晰的条件语句而即将成为 JavaScript 增强的功能。当写这篇文章时,它们还没有被完全支持,你需要使用 Babel 来编译。
可选链允许我们没有明确检查中间节点是否存在地处理 tree-like 结构,空值合并和可选链组合起来工作得很好,以确保为不存在的值赋一个默认值。
这有一个例子:
constcar={model:'Fiesta',manufacturer:{name:'Ford',address:{street:'SomeStreetName',number:'5555',state:'USA'}}}//togetthecarmodelconstmodel=car&&car.model||'defaultmodel';//togetthemanufacturerstreetconststreet=car&&car.manufacturer&&car.manufacturer.address&&car.manufacturer.address.street||'defaultstreet';//requestanun-existingpropertyconstphoneNumber=car&&car.manufacturer&&car.manufacturer.address&&car.manufacturer.phoneNumber;console.log(model)//'Fiesta'console.log(street)//'SomeStreetName'console.log(phoneNumber)//undefined
所以,如果我们想要打印是否车辆生产商来自美国,代码将看起来像这样:
constisManufacturerFromUSA=()=>{if(car&&car.manufacturer&&car.manufacturer.address&&car.manufacturer.address.state==='USA'){console.log('true');}}checkCarManufacturerState()//'true'
你能清晰地看到当有一个更复杂的对象结构时,这能变得多乱。有一些第三方的库有它们自己的函数,像 lodash 或 idx。例如 lodash 有 _.get 方法。然而,JavaScript 语言本身被引入这个特性是非常酷的。
这展示了这些新特性如何工作:
//togetthecarmodelconstmodel=car?.model??'defaultmodel';//togetthemanufacturerstreetconststreet=car?.manufacturer?.address?.street??'defaultstreet';//tocheckifthecarmanufacturerisfromtheUSAconstisManufacturerFromUSA=()=>{if(car?.manufacturer?.address?.state==='USA'){console.log('true');}}
这看起来很美观并容易维护。它已经到 TC39 stage 3 阶段,让我们等待它获得批准,然后我们就能无处不在地看到这难以置信的语法的使用。
总结
让我们为了编写更清晰、易维护的代码,学习并尝试新的技巧和技术,因为在几个月后,长长的条件看起来像搬石头砸自己的脚。????
若此文有用,何不素质三连❤️