“ 在项目开发中,由于之前项目难以维护,从而导致我们使用过多的if-else,这样导致写出来的代码又臭又长,给下面接手的同事留下了巨多问题,并且也加大了排查周期,接下来我们看看如何优化if else分支过多的问题。”
1. 三元运算符
其实在很多简单的判断情况下,我们可以放心的使用三元运算符去替代if-else语句,注意:三元运算符也不能嵌套太多层,因为它不具有较高的可读性,推荐一层嵌套是最优的。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 三元运算符使用的场景:条件赋值、递归等 // 条件赋值 const ageResult = (age) => { return age >= 18 ? '成年' : '未成年' } const printContent = ageResult(18) // 成年
// 递归 // 求整数和(0-n之间) let sum = 0 const toSum = (n) => { sum += n return n >= 2 ? toSum(n - 1) : sum } console.log(toSum(5)) // 15
|
2. 短路运算
其实在js语法中的或(||)运算,也就是我们说的短路运算,在一些比较简单的判断中也是可以替代if-else语句的。
举一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 逻辑或运算使用:如果左边的判断为true,则返回左边式子的值; // 否则返回右边式子的值。 // 优化之前的代码 let sum const num1 = 1 const num2 = 2 if (num1) { sum = num1 } else { sum = num2 }
// 优化之后的代码 const sum = num1 || num2
|
3.switch语句
在上面说的三元运算符和短路运算之后,它们两个代码虽然简洁,但是它们都只能用于简单的判断,如果当判断复杂且条件多时,就无法使用了。
这个时候我们就可以使用switch语句,switch语句对比if-else语句,它的可读性是比较高的,但是它写起来的话就比较麻烦的。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // 代码没有改造之前,使用if-else语句 let answer const val = '2' if (val === '1') { answer = 'a' } else if (val === '2') { answer = 'b' } else { answer = 'c' } console.log(answer) // 'b'
// 代码被改造后,使用switch语法 let answer const val = '2' switch (val) { case '1': answer = 'a' break case '2': answer = 'b' break default: answer = 'c' } console.log(answer) // 'b'
|
4. 对象配置/策略模式
对象配置/策略模式定义:根据不同的参数使用不同的数据/算法/函数,换句话说就是把他们一个个的封装起来,目的就是把算法的实现和使用分开来。
实现:通过一个对象或者其他hash数据结构,来映射不同的策略。
主要解决的问题:在有多种算法相似的情况下,使用if-else所带来的复杂和难以维护。
优点:(1)策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句(避免使用多重条件判断);(2)策略模式提供了开放-封闭原则,使代码更容易理解和扩展(扩展性好);(3)策略模式中的代码可以复用(复用性好);
缺点:(1)策略类会增多;(2)所有策略类都需要对外暴露。
使用场景:(1)需要动态地根据行为改变运行方法;(2)为了减少代码,同时为了让代码具有更好的可读性;(3)需要对策略进行统一管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| // 对象配置 const val = '2' let result = { 1: 'a', 2: 'b', default: 'c', } console.log(result[val]) // 'b'
// 策略模式 // 举一个例子:公司的年终奖根据员工的工资基数和绩效等级来发放的。 // 例如,绩效为A的人年终奖有4倍工资,绩效为B的有3倍,绩效为C的只有2倍。 // 代码没有改造之前 const calculateBonus = (performanceLevel, salary) => { if (performanceLevel === 'A'){ return salary * 4 } if (performanceLevel === 'B'){ return salary * 3 } if (performanceLevel === 'C'){ return salary * 2 } } calculateBonus('A', 10000) // 输出:40000
|
上面计算年终奖例子中,我们可以看到calculateBonus
函数整体计算是很庞大的,然后逻辑分支都包括在if-else语法中,如果当我们又增加了一种新的绩效等级D或者又更改等级系数时,那么又需要去读取calculateBonus
函数然后做对应的修改。
1 2 3 4 5 6 7 8 9 10
| // 代码改造之后之使用策略模式 let strategies = new Map([ ['A', 4], ['B', 3], ['C', 2] ]) const calculateBonus = (performanceLevel, salary) => { return strategies.get(performanceLevel) * salary } calculateBonus('A', 10000) // 输出:40000
|
在js中,函数是一等公民,策略模式的使用常常隐藏在高阶函数中,稍微变换下上述形式,可以发现我们平时已经在使用这种模式了,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| // 创建策略对象,这个策略对象中包含了不同的计算策略,其实也是对象的方法; // 我们将不同的计算方法放到了一起作为对象的方法,就相当于把他们组合放置 // 在一个篮子里,之后需要哪个就去拿哪个 const strategies = { A: function (salary) { return salary * 4 }, B: function (salary) { return salary * 3 }, C: function (salary) { return salary * 2 }, } // 创建一个计算函数 // 相比使用类的方式,使用策略模式会更清晰明了。再相比最开始直接使用 // if-else的方法,省去了条件判断,增加了程序的弹性、易扩展。 function calculateBouns (performanceLevel, salary) { return strategies[performanceLevel](salary); } console.log(calculateBouns('A', 10000)) // 40000
// 对上面我们还可以使用隐藏在策略模式下的高阶函数 const s1 = (salary) => { return salary * 4 } const s2 = (salary) => { return salary * 3 } const s3 = (salary) => { return salary * 2 } const calculateBonus = (func, salary) => { return func(salary) } calculateBonus(s1, 10000) // 40000
|
5. 总结
我们再结合上述代码来看策略模式的定义:其实就是定义一系列的算法,然后把它们各自封装成策略类,算法就被封装在策略类内部的方法里,供外部直接调用而不用关心策略的具体实现。
策略模式程序至少由两部分组成:
(1)一组策略类,策略类封装了具体的算法,并负责具体的计算过程(上面例子中就是我们定义的strategies来充当);
(2)环境类Context,也叫做Context上下文,Context接受客户的请求,随后把请求委托给某一个策略类,要做到这点,说明Context中要维持对某个策略对象的引用(上面例子中Context上下文由calculateBouns充当)。
其实策略模式的实现并不复杂,关键是如何从策略模式的实现背后,找到封装变化、委托和多态性这些思想的价值。
6. 延展
我们再来举一个表单的例子,下面表单验证的规则如下:
(1)用户名不为空
(2)密码长度不能少于六位
(3)手机号码必须符合样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <form action="http://xxx.com" id="registerForm" method="post"> 输入用户名:<input type="text" name="userName" /> 输入密码:<input type="text" name="password" /> 输入手机号码:<input type="text" name="phoneNumber" /> <button>提交</button> </form>
// 传统方式验证表单,可以看到验证算法通过if语句来实现,直接包含在表单提交方法里, // 如果后期增加规则的话,还要来改动表单提交方法中的表单验证这一段代码,扩展性极差 const registerForm = document.getElementById('registerForm'); registerForm.onsubmit = () => { if (registerForm.userName.value === '') { alert('用户名不能为空'); return false; } if (registerForm.password.value.length < 6) { alert('密码长度不能少于6 位'); return false; } if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) { alert('手机号码格式不正确'); return false; } }
|
优化之后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| // 接下来我们通过策略模式来优化代码 // 将验证规则封装成策略对象 const strategies = { isNonEmpty: (value, errorMsg) => { // 不为空 if (value === '') { return errorMsg; } }, minLength: (value, length, errorMsg) => { // 限制最小长度 if (value.length < length) { return errorMsg; } }, isMobile: (value, errorMsg) => { // 手机号码格式 if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }
// Validator仅仅负责用户的请求,并将它委托给strategies策略对象 class Validator { constructor() { this.cache = [] // 保存校验规则 } add(dom, rule, errorMsg) { const ary = rule.split(':') this.cache.push(() => { const strategy = ary.shift() // 把input的value添加进参数列表 ary.unshift(dom.value) ary.push(errorMsg) return strategies[strategy].apply(dom, ary) }) } start() { for (let i = 0; i < this.cache.length; i++) { const validatorFunc = this.cache[i] const msg = validatorFunc(); // 开始校验,并取得校验后的返回信息 if (msg) { // 如果有确切的返回值,说明校验没有通过 return msg; } } } }
const validataFunc = function () { const validator = new Validator() validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空') validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位') validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确') const errorMsg = validator.start() // 获得校验结果 return errorMsg; // 返回校验结果 }
const registerForm = document.getElementById('registerForm'); registerForm.onsubmit = function () { // 如果errorMsg 有确切的返回值,说明未通过校验 const errorMsg = validataFunc() if (errorMsg) { alert(errorMsg) return false // 阻止表单提交 } }
|
代码优化之后,我们是将验证规则封装到了strategies策略对象中,各个验证规则的实现都是在这个策略对象中实现的;然后定义了一个Validator类,这个类是用来接收用户的请求的,然后将其委托给strategies策略对象去执行验证;validataFunc方法中是定义用户请求发送的,在validataFunc方法中我们实例化了Validator类。
提升代码可读性,减少 if-else 的几个小技巧
干掉过多if-else,试试状态模式!
【JavaScript设计模式】策略模式