js-函数最佳实践

74 阅读4分钟

函数最佳实践

函数最佳实践

  • 避免回调
    回调很混乱,会导致代码嵌套过深,使用 Promise 替代回调。

    bad : ❌
    getUser(function (err, user) {
      getProfile(user, function (err, profile) {
        getAccount(profile, function (err, account) {
          getReports(account, function (err, reports) {
            sendStatistics(reports, function (err) {
              console.error(err);
            });
          });
        });
      });
    });
    
    good ✅ 
    getUser()
      .then(getProfile)
      .then(getAccount)
      .then(getReports)
      .then(sendStatistics)
      .catch((err) => console.error(err));
    
    good  or using Async/Await ✅✅
    
    async function sendUserStatistics() {
      try {
        const user = await getUser();
        const profile = await getProfile(user);
        const account = await getAccount(profile);
        const reports = await getReports(account);
        return sendStatistics(reports);
      } catch (e) {
        console.error(err);
      }
    }
    
  • **避免过长参数:**函数规范,参数尽量限制为3个以内;三个及以上,需要考虑独立为JSON对象,通过es6解构获取

    bad : ❌
    const setUserInfo = function( id, name, address, sex, mobile){
      console.log(id,name,address,sex,mobile)
    };
    
    setUserInfo( '0921', 'ricky', 'sh', 'male', '131********');
    
    good ✅
    const setUserInfo = function( obj ){
      const {id,name,address,sex,mobile} = obj;
      console.log(id,name,address,sex,mobile)
    };
    
    setUserInfo({
      id: '0921',
      name: 'ricky',
      address: 'sh',
      sex: 'male',
      mobile: '137********',
    });
    
  • 尽早从函数返回,减少分支

    
     bad : ❌
     const del = function( obj ){
       let ret;
       if ( !obj.isReadOnly ){  // 不为只读的才能被删除
         if ( obj.isFolder ){  // 如果是文件夹
           ret = deleteFolder( obj );
         }else if ( obj.isFile ){  // 如果是文件
           ret = deleteFile( obj );
         }
       }
       return ret;
     };
    
     good ✅
     const del = function( obj ){
       if ( obj.isReadOnly ){  // 反转if表达式
         return;
       }
       if ( obj.isFolder ){
         return deleteFolder( obj );
       }
       if ( obj.isFile ){
         return deleteFile( obj );
       }
     };
     //减少了临时变量,并提前返回
    
  • 分解条件表达式

    bad : ❌
    const ieIEMac = navigator.userAgent.toLowerCase().includes("mac") && navigator.userAgent.toLowerCase().includes("ie") 
    不利于阅读
    
    good ✅
    const userAgent = navigator.userAgent.toLowerCase(); 
    const isMac = () => userAgent.includes("mac"); 
    const isIE = () => userAgent.toLowerCase().includes("ie"); 
    const isMacIE = isMac() && isIE(); 
    
  • 函数最好使用参数默认值

      bad : ❌
      function fn (name, age) {
        var name = name || 'ricky'
        var age = age || 18
        console.log(name, age)
      }
      fn() // ricky 18
    
      goodfunction fn (name = 'ricky', age = 18) {
        console.log(name, age)
      }
      fn() // ricky 18
    
  • switch 或者 if elseif过多,可以考虑多态重构

    使用类及多态 ,可以把逻辑的拆分表述的更清晰
    可以针对 switch语句中的每种分支逻辑创建一个类,用多态来承载各个类型特有的行为,从而除去复杂的分支 逻辑

    
    switch(bird.type){
        case 'EuropeanSwallow':
            return 'average';
        case 'AfricanSwallow':
            return (bird.numberOfCocounts > 2) ? 'tired' : 'average';
        case 'NorwegianBlueParrot':
            return (bird.voltage >100 ) ? 'scorched' : 'beautiful';
        default:
            return 'unkonwn';
    }
    
    class EuropeanSwallow extends Bird {
        get plumage() {
            return 'average';
        }
    }
    
    class AfricanSwallow extends Bird{
        get plumage(){
            return (this.numberOfCocounts > 2) ? 'tired' : 'average';
        }
    }
    
    class NorwegianBlueParrot extends Bird{
        get plumage(){
            return (this.voltage >100 ) ? 'scorched' : 'beautiful';
        }
    }
    

    优先使用MAP来减少

    // bad ❌
    const getColorByStatus = (status) => {
      switch (status) {
        case "success":
          return "green";
        case "failure":
          return "red";
        case "warning":
          return "yellow";
        case "loading":
        default:
          return "blue";
      }
    };
    
    // good ✅
    const statusColors = {
      success: "green",
      failure: "red",
      warning: "yellow",
      loading: "blue",
    };
    
    const getColorByStatus = (status) => statusColors[status] || "blue";
    
  • 参数复用,柯里化

   
    本示例只是表达可以通过柯里化 展现可以复用参数的一种方式
    function check(reg,txt){
        return reg.test(txt)
    }

    bad : ❌
    // 需要传两个参数
    check(/\d+/g,'test1') //true
    check(/\d+/g,'test') //false

    good:✅
    let curryingCheck = currying(check)
    var hasNumber = curryingCheck(/\d+/g);
    hasNumber('test1') //true
    hasNumber('test') //false
    
    //柯里化的实现函数,供参考
    function currying(fun,initArgs){
        let len = fun.length;
        let _t = this;
        let args = initArgs || [];

        return function() {
            let _args = [...args, ...arguments]; //参数
            if(_args.length <len){
                return currying.call(_t,fun,_args);
            }
            return fun.apply(this,_args);
        }
    }
  • 管道函数替代循环

     bad : ❌
     const names=[];
     for(const i of input){
        if(i.job === 'programmer'){
          names.push(i.name);
        }
     }
    
     good ✅
     const names =input.filter(i=>i.job === 'programmer').map(i => i.name)
    
  • 闭包记住的是 变量的引用,而不是闭包创建时刻该变量的值

     bad : ❌
     const divs = document.getElementsByTagName("div");
      for(var i=0;i< divs.length;i++){
        divs[i].addEventListener("click",function(){
          alert("divs #" +i + " was clicked.");
        },false);
      }
    
      bad : ❌
      for(var i=0;i< divs.length;i++)(function(n){
          divs[n].addEventListener("click",function(){
            alert("divs #" +n + " was clicked.");
          },false);
      })(i);
    
      good ✅
      for(let i=0;i< divs.length;i++){
        divs[i].addEventListener("click",function(){
          alert("divs #" +i + " was clicked.");
        },false);
      }
    
  • settimeout/setInterval 需要清除

  • 避免无限递归,要有终止条件

    bad : ❌
    const repeat = (n) => {return  repeat(n-1)+'-repeat'} 
    // Uncaught RangeError: Maximum call stack size exceeded
    
    good ✅
    const repeat = (n) => {return n >1 ? repeat(n-1)+'-repeat':'repeat'} //'repeat-repeat-repeat'
    

数组最佳实践

尽量考虑使用lodash提供的方法

  • 使用...

    
    bad : ❌
    const a = [1,2,3];
    const b = [1,5,6];
    const c = a.concat(b);//[1,2,3,1,5,6]
    
    const obj1 = {
      a:1,
    }
    const obj2 = {
      b:1,
    }
    const obj = Object.assign({}, obj1, obj2);//{a:1,b:1}
    
    good ✅
    const a = [1,2,3];
    const b = [1,5,6];
    const c = [...new Set([...a,...b])];//去重 [1,2,3,5,6]
    
    const obj1 = {
      a:1,
    }
    const obj2 = {
      b:1,
    }
    const obj = {...obj1,...obj2};//{a:1,b:1}
    
  • 使用Array.from 将类数组对象转化为数组,const arr = Array.form(arrLike)

  • 优先使用includes减少if判断条件

    bad : ❌
    if(
        type == 1 ||
        type == 2 ||
        type == 3 ||
        type == 4 ||
    ){
       //...
    }
    
    good ✅
    const condition = [1,2,3,4];
    if( condition.includes(type) ){
       //...
    }
    
  • 使用Array.find查找

    const data = [
      {
        type: 'test1',
        name: 'abc'
      },
      {
        type: 'test2',
        name: 'cde'
      },
      {
        type: 'test1',
        name: 'fgh'
      },
    ]
    
    bad : ❌
    
    function findtest1(name) {
      for (let i = 0; i < data.length; ++i) {
        if (data[i].type === 'test1' && data[i].name === name) {
          return data[i];
        }
      }
    }
    
    good ✅ 
    
    filteredData = data.find(data => data.type === 'test1' && data.name === 'fgh');
    console.log(filteredData); // { type: 'test1', name: 'fgh' }
    
  • 使用for-of或数组方法替代for(;;)

    const arr=[1,2,3,4,5];
    
    bad : ❌
    for(let i=0;i<arr.length;i++){
      console.log(arr[i]);
    }
    
    good ✅
    for(let item of arr) {
      console.log(item)
    }
    1 2 3 4 5
    
  • 扁平化数组

    
    //一个部门JSON数据中,属性名是部门id,属性值是个部门成员id数组集合,现在要把有部门的成员id都提取到一个数组集合中。
    const deps = {
    '采购部':[1,2,3],
    '人事部':[5,8,12],
    '行政部':[5,14,79],
    '运输部':[3,64,105],
    }
    
    bad : ❌
    let member = [];
    for (let item in deps){
        const value = deps[item];
        if(Array.isArray(value)){
            member = [...member,...value]
        }
    }
    member = [...new Set(member)]
    
    good ✅
    
    let member = Object.values(deps).flat(Infinity);