Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化JavaScript代码的细节 #15

Open
chiwent opened this issue Aug 8, 2019 · 0 comments
Open

优化JavaScript代码的细节 #15

chiwent opened this issue Aug 8, 2019 · 0 comments

Comments

@chiwent
Copy link
Owner

chiwent commented Aug 8, 2019

优化JavaScript代码的细节

1. 关于条件语句

1.处理if语句中对单个变量值的多次逻辑运算

通常,我们在一个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) {
            // ...
        }
    });
}

2.用return提前返回,减少if嵌套

在开发中,我们可能会遇到这个代码场景,需要在第一层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

3.将默认值提升到默认参数中

举个在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) {
            // ...
        }
    }
}

4.使用every/some处理全部/部分满足条件

没有优化过的代码如下:

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。

5.删除冗余的if...else if...if语句

在开发中,对单个变量的多个条件进行判断,就可能产生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);
    }
});

2.关于出入参

1.消除大量的入参

在调用函数时,可能需要对函数传入大量的参数,日积月累,函数的入参可能会随着功能的迭代不断增加,所以我们需要将这些参数放入对象中:

// 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),这样的代码就很冗余,而且嵌套的层级越多,代码越啰嗦,那么有什么方法可以改善这种情况呢?

  • 1.try-catch实现(最简便的方法)
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.遍历取值
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'])




未完待续......

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant