Open chiwent opened 5 years ago
通常,我们在一个if判断语句中可能会对单个变量进行多次的比较,然后再取逻辑或,比如下面
function test(param) { if (param === 1 || param === 2 || param === 3) { // ... } }
这样,if语句不仅会变得累赘,而且一旦增加判断条件,那么就要在if语句中继续加入条件,不利于扩展。我们可以将判定的阈值放在数组中,然后通过Array.includes去查询是否有符合数组内元素的条件:
function test(param) { let arr = [1, 2, 3]; if (arr.includes(param)) { // ... } }
或者也可以使用Array.some实现,不过在这个情景中还是推荐使用includes:
function test(param) { let arr = [1, 2, 3]; arr.some(item => { if (item === param) { // ... } }); }
在开发中,我们可能会遇到这个代码场景,需要在第一层if中对某个值进行判断其是否符合条件,然后在该if判断语句中再进行其他的逻辑判断,这样就增加了if的嵌套层数:
function test(arr, param) { if (arr.length > 0) { if (param === 1) { // ... } } }
在上面的代码中,只有当传入的参数arr不为空数组时,才会进入到主体的操作。 其实我们完全将第一层if判断抽离到顶部,在判断不符合条件就马上return:
function test(arr, param) { if (arr.length === 0) { return; } if (param === 1) { // ... } }
所以,在遇到类似上述的if嵌套判断时,我们可以在发现无效条件时提前return
举个在vue代码中常见的栗子,如果我们要对data的默认值和传入的参数进行逻辑或,可能会有下面的代码:
export default { data() { return { condition: 1 } }, methods: { test(param) { const _data = param || this.condition; this.excute(_data); }, excute(param) { // ... } } }
在上述的test方法中,如果传入的param不为空,我们就在excute中传入param;如果param为空,就使用默认的this.condition值。实际上,如果我们将默认值提前,就可以减少_data这个临时变量:
export default { data() { return { condition: 1 } }, methods: { test(param = this.confition) { // const _data = param || this.condition; // 减少了这一句 this.excute(_data); }, excute(param) { // ... } } }
没有优化过的代码如下:
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 }
上述代码的意图就是为了判断所有的水果是否全为红色。
在处理全部/部分满足的条件时,我们通常可以使用every/some来进行处理:
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 }
同理,如果是要检查是否至少有一类水果是红色的,就将every改写为some。
在开发中,对单个变量的多个条件进行判断,就可能产生if判断的面条代码。在这种情况下,我们可以使用switch...case进行初步优化:
function test(color) { // 使用 switch case 语句,根据颜色找出对应的水果 switch (color) { case 'red': return ['apple', 'strawberry']; case 'yellow': return ['banana', 'pineapple']; case 'purple': return ['grape', 'plum']; default: return []; } }
不过switch...case依旧不是最优的处理方式。假如在判断条件内没有进行太复杂的运算,我们可以考虑使用高阶函数、Map对象和Object字面量来处理,比如:
// 方法1:Object字面量 const fruitColor = { red: ['apple', 'strawberry'], yellow: ['banana', 'pineapple'], purple: ['grape', 'plum'] }; function test(color) { return fruitColor[color] || []; } // 方法2:Map对象 const fruitColor = new Map() .set('red', ['apple', 'strawberry']) .set('yellow', ['banana', 'pineapple']) .set('purple', ['grape', 'plum']); function test(color) { return fruitColor.get(color) || []; } // 方法3:高阶函数 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 test(color) { return fruits.filter(f => f.color == color); }
除了上述的方式,我们也可以使用策略模式进行优化,常见的场景就是表单验证,由于需要对表单值进行多条件的判断,可能会产生较长的if...else...if语句,可以使用策略模式优化,详情见:探索两种优雅的表单验证——策略设计模式和ES6的Proxy代理模式
使用策略模式来优化表单校验:
/*策略对象*/ const strategies = { isNonEmpty(value, errorMsg) { return value === '' ? errorMsg : void 0 }, minLength(value, length, errorMsg) { return value.length < length ? errorMsg : void 0 }, isMoblie(value, errorMsg) { return !/^1(3|5|7|8|9)[0-9]{9}$/.test(value) ? errorMsg : void 0 }, isEmail(value, errorMsg) { return !/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value) ? errorMsg : void 0 } } class Validator { constructor() { this.ruleList = []; } add(dom, rules) { for (let rule of rules) { // 策略规则,如'minLength:6',会拆分为['minLength', '6']; const strategyAry = rule.strategy.split(':'); // 错误信息 const errMsg = rule.errMsg; this.ruleList.push(() => { // 策略规则 const strategy = strategyAry.shift(); strategyAry.unshift(dom.value); strategyAry.push(errMsg); return strategies[strategy].apply(dom, strategyAry); }); } } start() { for (let validatorFunc of this.ruleList) { const errMsg = validatorFunc(); if (errMsg) { return errMsg; } } return false; } } // 使用 const form = document.getElementById('#form'); const validatorFunc = () => { const validator = new Validator(); validator.add(form.userName, [{ strategy: 'isNonEmpty', errMsg: '用户名不能为空', }, { strategy: 'minLength:6', errMsg: '用户名长度不能小于六位' }]); validator.add(form.password, [{ strategy: 'isNonEmpty', errMsg: '密码不能为空', }, { strategy: 'minLength:6', errMsg: '密码长度不能小于六位' }]); const errMsg = validatorFunc(); return errMsg; } form.addEventListener('submit', () => { const errMsg = validatorFunc(); if (errMsg) { alert(errMsg); } });
在调用函数时,可能需要对函数传入大量的参数,日积月累,函数的入参可能会随着功能的迭代不断增加,所以我们需要将这些参数放入对象中:
// bad function test(a, b, c, d, e) { console.log(a) console.log(b) console.log(c) console.log(d) console.log(e) } test(1, 2, 3, 4, 5); // good function test(obj) { console.log(obj.a) console.log(obj.b) console.log(obj.c) console.log(obj.d) console.log(obj.e) } test({ a: 1, b: 2, c: 3, d: 4, e: 5 })
在实际开发中,我们经常会对一个多层嵌套的对象进行判断,比如:if(obj.a.b.c),但是假如obj这个对象为空,或者其中的某个属性为空,那么代码就会报错。之后,我们可能就会写出这样的代码:if (obj && obj.a && obj.a.b && obj.a.b.c),这样的代码就很冗余,而且嵌套的层级越多,代码越啰嗦,那么有什么方法可以改善这种情况呢?
if(obj.a.b.c)
if (obj && obj.a && obj.a.b && obj.a.b.c)
function judgeObjProperty(obj) { try { return obj.a.b.c; } catch (err) { return undefined; } }
// 直接使用 const property = judgeObjProperty(); if (property) { // ... }
// 稍微封装一下 function judgeObjProperty(property) { try { return property; } catch (err) { return undefined; } }
// 使用: const property = judgeObjProperty(obj.a.b.c); if (property) { // ... }
// 或者: function judgeObjProperty(data, expression) { try { return eval('data' + expression) } catch (error) { return undefined } }
// 使用: judgeObjProperty(obj, '.a.b.c');
- 2.遍历取值 ```js function judgeObjProperty(data, keys) { for(let key of keys) { if(data[key] === undefined) { return undefined } else { data = data[key] } } return data } judgeObjProperty(bj, ['a', 'b', 'c'])
未完待续......
优化JavaScript代码的细节
1. 关于条件语句
1.处理if语句中对单个变量值的多次逻辑运算
通常,我们在一个if判断语句中可能会对单个变量进行多次的比较,然后再取逻辑或,比如下面
这样,if语句不仅会变得累赘,而且一旦增加判断条件,那么就要在if语句中继续加入条件,不利于扩展。我们可以将判定的阈值放在数组中,然后通过Array.includes去查询是否有符合数组内元素的条件:
或者也可以使用Array.some实现,不过在这个情景中还是推荐使用includes:
2.用return提前返回,减少if嵌套
在开发中,我们可能会遇到这个代码场景,需要在第一层if中对某个值进行判断其是否符合条件,然后在该if判断语句中再进行其他的逻辑判断,这样就增加了if的嵌套层数:
在上面的代码中,只有当传入的参数arr不为空数组时,才会进入到主体的操作。
其实我们完全将第一层if判断抽离到顶部,在判断不符合条件就马上return:
所以,在遇到类似上述的if嵌套判断时,我们可以在发现无效条件时提前return
3.将默认值提升到默认参数中
举个在vue代码中常见的栗子,如果我们要对data的默认值和传入的参数进行逻辑或,可能会有下面的代码:
在上述的test方法中,如果传入的param不为空,我们就在excute中传入param;如果param为空,就使用默认的this.condition值。实际上,如果我们将默认值提前,就可以减少_data这个临时变量:
4.使用every/some处理全部/部分满足条件
没有优化过的代码如下:
上述代码的意图就是为了判断所有的水果是否全为红色。
在处理全部/部分满足的条件时,我们通常可以使用every/some来进行处理:
同理,如果是要检查是否至少有一类水果是红色的,就将every改写为some。
5.删除冗余的if...else if...if语句
在开发中,对单个变量的多个条件进行判断,就可能产生if判断的面条代码。在这种情况下,我们可以使用switch...case进行初步优化:
不过switch...case依旧不是最优的处理方式。假如在判断条件内没有进行太复杂的运算,我们可以考虑使用高阶函数、Map对象和Object字面量来处理,比如:
除了上述的方式,我们也可以使用策略模式进行优化,常见的场景就是表单验证,由于需要对表单值进行多条件的判断,可能会产生较长的if...else...if语句,可以使用策略模式优化,详情见:探索两种优雅的表单验证——策略设计模式和ES6的Proxy代理模式
使用策略模式来优化表单校验:
2.关于出入参
1.消除大量的入参
在调用函数时,可能需要对函数传入大量的参数,日积月累,函数的入参可能会随着功能的迭代不断增加,所以我们需要将这些参数放入对象中:
对象操作
多嵌套对象属性的判断
在实际开发中,我们经常会对一个多层嵌套的对象进行判断,比如:
if(obj.a.b.c)
,但是假如obj这个对象为空,或者其中的某个属性为空,那么代码就会报错。之后,我们可能就会写出这样的代码:if (obj && obj.a && obj.a.b && obj.a.b.c)
,这样的代码就很冗余,而且嵌套的层级越多,代码越啰嗦,那么有什么方法可以改善这种情况呢?// 直接使用 const property = judgeObjProperty(); if (property) { // ... }
// 稍微封装一下 function judgeObjProperty(property) { try { return property; } catch (err) { return undefined; } }
// 使用: const property = judgeObjProperty(obj.a.b.c); if (property) { // ... }
// 或者: function judgeObjProperty(data, expression) { try { return eval('data' + expression) } catch (error) { return undefined } }
// 使用: judgeObjProperty(obj, '.a.b.c');
未完待续......