最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【总结】代码优化篇

    正文概述 掘金(just do it君)   2021-08-12   431

    记住,您编写的每一行代码都会有相应的阅读成本。看这代码的人可能是团队的成员,甚至是你未来的自己。

    一、软件设计3R层次结构

    • readable可读性
    • reuseable可复用性
    • refactorable可重构性

    【总结】代码优化篇

    关键思想:

    • 保持代码一致:一致的风格比“正确”的风格更重要。——编码规范、ESlint
    • 易于阅读和理解:应当使别人理解它所需的时间最小化。——命名、函数式编程
    • 易于维护。——设计模式思想

    二、为什么我极力推荐eslint

    据广泛估计,开发人员将70%的代码维护时间花在阅读上以理解它。这真让人大开眼界。居然达到了70%。难怪程序员每天编写的代码行数的平均值大约是10行。我们每天花7个小时来阅读代码,去理解这10行怎么运行!

    团队合作带来的问题

    看别人的代码

    • 团队其他人和自己书写代码的风格不一致,很难去适应别人的编程风格。
    • 甚至一个文件有多个人改动过,有多种编程风格,看着不仅难受,阅读代码也较吃力。

    改别人的代码

    • 代码没有格式化便提交,增加团队成员的阅读成本。
    • 团队成员使用不同的编辑器,每个人都自有一套格式化代码风格,很容易因格式化代码生成多余的改动,从而产生冲突,也不利于解决。
    • 代码提交到git上,由于格式化带来的改动,反而不利于查看这次提交实际改动了哪些代码。

    eslint解决了什么

    • 不要使用编辑器自带的格式化,用在项目中配置的eslint的规则去格式化代码,团队使用一致的编程风格,会让代码更容易阅读。
    • 设置每次保存的时候就格式化代码,不用每次都手动输入空格或者分号来保持编程风格,也不用每次都手动格式化代码。
    • 每次提交代码,不会因为代码格式化问题产生多余的改动,干净的提交,便于以后查看提交记录解决问题。

    经过一年多使用eslint的切身体会,我确实感受到eslint的好用之处,以及在团队协作中带来的好处,让你在修改别人写的代码的时候,也不会因为不统一的代码风格难受。

    三、项目中的优化

    下面我们将从代码的易读、性能等方面,举例说明在实际项目中我们可以怎么做来优化我们的代码。

    命名

    【总结】代码优化篇

    坏味道:命名采用缩写。

    推荐:选择具体有实际意义的词,不要用别人看不懂的缩写。

    函数

    【总结】代码优化篇 坏味道:函数参数太多,命名模糊。

    推荐:函数参数不要超过2个,超过的话可以考虑用对象传参,获取参数的时候用解构赋值。

    【总结】代码优化篇 坏味道:函数过大,难于阅读。

    推荐:一个函数只做一件事原则,把代码片段提取出来放入一个独立的函数,这样的好处是函数名也起到了注释的效果,有助于阅读代码,提取出来的函数有助于代码复用。

    值的不变性

    重新赋值

    var/let声明的值可被重新赋值,var可能引起变量提升,带来隐藏的问题。

    let x = 1;
    x = 2;
    

    const声明的值不可被重新赋值,但如果声明的变量是引用类型,值的内部仍然可被改变

    const x = 2;
    // 试着改变`x`看看!
    x = 3;      // Error!
    
    const x = [ 2 ];
    x[0] = 3; // 可以改变
    x = [1]; // Error!
    

    推荐:程序中尽量避免var的使用,因为var会带来作用域的问题;用const声明不会被改变的变量。

    值的冻结

    Object.freeze:将一个可变的对象/数组/函数转换成一个“不可变值”,但只针对最顶层。

    var x = Object.freeze( [ 2, 3, [4, 5] ] );
    
    // 不允许:
    x[0] = 42;
    
    // 允许:
    x[2][0] = 42;
    

    Object.freeze(..)提供了浅的、单一的不变性,如果您想要一个非常不可变的值,就必须手动遍历整个对象/数组结构,并对每个子对象/数组应用Object.freeze(..)。

    推荐:不会被修改的对象/数组用Object.freeze“冻结”起来。

    for循环

    for循环中的代码会在每次循环的时候都会执行,不要在循环里写无意义的代码。

    推荐

    • 将一些操作放在循环体外面
    • 如果要提前进入下一次循环,用continue
    • 如果要提前退出循环,用break

    多个弹窗的情况

    我们页面中有多个弹窗,需要用变量控制弹窗的显示隐藏

    【总结】代码优化篇

    坏味道:一个变量控制一个弹窗的显示隐藏,变量数很多,容易混淆哪个变量控制哪个弹窗

    推荐:通过给变量赋值,判断变量是否与值相等来控制弹窗显示隐藏

    合理使用数据结构

    // 坏味道
    data() {
      return {
        total: 0,
        pageSize: 12,
        currentPage: 1,
        name: '',
      }
    }
    
    // 推荐
    data() {
      return {
        queryInfo: {
          total: 0,
          pageSize: 12,
          currentPage: 1,
          name: '',
        },
      }
    }
    

    坏味道:数据过于扁平,变量很多

    推荐:把相关功能的几个变量封装在一个对象里面,对象名起到注释作用,说明这些变量的作用

    在调后端接口获取数据传参的时候,如果对象的属性和我们要传给接口的参数一直,那么传参的时候只需要传一个对象就可以了,不用再写一遍。

    适当复用代码

    DRY(dont repeat yourself)原则。

    1.常用工具方法

    坏味道:在组件里面用到什么方法复制粘贴一遍

    推荐:像格式化时间、判断浏览器类型、防抖节流等等,整理出来放在一个文件里面,导出方法,在用到的地方导入

    【总结】代码优化篇

    2.使用mixins

    坏味道:在组件把类似功能的代码复制粘贴一遍

    推荐:类似的功能提取出来放在mixins里面,同时也要考虑复用性。比如table相关的变量和方法

    【总结】代码优化篇

    条件判断

    多重条件,即代码逻辑中有很多条件判断语句,比如if-else分支。

    1.使用布尔值的快捷方式

    // 坏味道
    if (isValid === true) {}
    if (arr.length === 0) {}
    
    // 推荐
    if (isValid) {}
    if (!arr.length) {}
    

    2.合理使用三元语句

    简单的两重条件判断可以用三元表达式代替:

    // 坏味道
    if (a === b) {
      res = true;
    } else {
      res = false;
    }
    
    // 推荐
    res = a === b ? true : false;
    

    同时也要避免不必要的三元表达式:

    // 坏味道
    const res = a ? a : b;
    
    // 推荐
    const res = a || b;
    

    3.把复杂的条件分支语句提炼成函数

    // 坏味道
    var getPrice = function (price) {
      var date = new Date();
      if (date.getMonth() >= 6 && date.getMonth() <= 9) {
        // 夏天
        return price * 0.8;
      }
      return price;
    };
    
    // 推荐
    var isSummer = function () {
      var date = new Date();
      return date.getMonth() >= 6 && date.getMonth() <= 9;
    };
    var getPrice = function (price) {
      if (isSummer()) {
        // 夏天
        return price * 0.8;
      }
      return price;
    };
    

    4.避免条件判断的重复过程

    // 惰性加载函数
    // 坏味道
    var addEvent = function (elem, type, handler) {
      if (window.addEventListener) {
        return elem.addEventListener(type, handler, false);
      }
      if (window.attachEvent) {
        return elem.attachEvent("on" + type, handler);
      }
    };
    // 推荐
    var addEvent = function (ele, type, handler) {
      if (window.addEventListener) {
        addEvent = function (ele, type, handler) {
          ele.addEventListener(ele, type, handler, false);
        };
      } else if (window.attachEvent) {
        addEvent = function (ele, type, handler) {
          ele.addEventListener(ele, type, handler);
        };
      }
    
      addEvent(ele, type, handler);
    };
    

    5.巧用return

    1.提前return

    // 坏味道
    var del = function (obj) {
      var ret;
      if (!obj.isReadOnly) {
        // 不为只读的才能被删除
        if (obj.isFolder) {
          // 如果是文件夹
          ret = deleteFolder(obj);
        } else if (obj.isFile) {
          // 如果是文件
          ret = deleteFile(obj);
        }
      }
      return ret;
    };
    
    // 推荐
    var del = function (obj) {
      if (obj.isReadOnly) {
        // 反转 if 表达式
        return;
      }
      if (obj.isFolder) {
        return deleteFolder(obj);
      }
      if (obj.isFile) {
        return deleteFile(obj);
      }
    };
    

    优点

    • 更少的嵌套,看起来更简洁
    • 程序不用再往下执行

    缺点:提前返回导致函数有多个输出,可能难以读取函数以了解其输出行为,在流控制结构(if逻辑等等)中,会导致代码可读性更差。

    现在大多数文章都在推崇尽可能早的return,但我们也不应该忽视它可能会带来的问题,所以在实际项目中,我们也要合理选择在什么时候return。

    2.用return退出多重循环

    // 坏味道
    var func = function () {
      var flag = false;
      for (var i = 0; i < 10; i++) {
        for (var j = 0; j < 10; j++) {
          if (i * j > 30) {
            flag = true;
            break;
          }
        }
        if (flag === true) {
          break;
        }
      }
      
      // do something
      console.log(i);
    };
    
    // 推荐
    var print = function (i) {
      console.log(i);
    };
    var func = function () {
      for (var i = 0; i < 10; i++) {
        for (var j = 0; j < 10; j++) {
          if (i * j > 30) {
            return print(i);
          }
        }
      }
    };
    

    6.if-else的替代写法

    实际项目中的例子: 【总结】代码优化篇 程序中大量的if-else阅读起来比较吃力,但实际我们可以有很多写法来代替。下面会举例一些场景的代码,再结合设计模式中的一些思想,说说怎么处理这些多重条件的情况更好。

    6.1 策略模式

    定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

    JavaScript语言的特性,决定了我们实用策略模式很简单。

    // 坏味道
    function travel(type) {
      if (type === "plane") {
        //...
      } else if (type === "train") {
        //...
      } else if (type === "bus") {
        //...
      }
    }
    
    // 推荐
    function travel(type) {
      const options = {
        plane: () => {},
        train: () => {},
        bus: () => {},
      };
    
      return options[type]();
    }
    

    6.2 迭代器模式

    定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示,又叫遍历器。

    Array.prototype.includes采用的就是迭代器的思想:

    // 坏味道
    if (color === "red" || color === "green" || color === "blue") {
      console.log(color + "属于三原色");
    }
    
    // 推荐
    if (["red", "green", "blue"].includes(color)) {
      console.log(color + "属于三原色");
    }
    

    我们再看一个更复杂的文件上传例子

    // 迭代器实现 上传文件
    // 坏味道
    var getUploadObj = function () {
      try {
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
      } catch (e) {
        if (supportFlash()) {
          // supportFlash 函数未提供
          var str = '<object type="application/x-shockwave-flash"></object>';
          return $(str).appendTo($("body"));
        } else {
          var str = '<input name="file" type="file"/>'; // 表单上传
          return $(str).appendTo($("body"));
        }
      }
    };
    
    // 推荐
    var getActiveUploadObj = function () {
      try {
        return new ActiveXObject("EXFTNActiveX.FTNUpload");
      } catch (e) {
        return false;
      }
    };
    
    var getFlashUploadObj = function () {
      if (supportFlash()) {
        var str = '<object type="application/x-shockwave-flash"></object>';
        return $(str).appendTo($("body"));
      }
      return false;
    };
    
    var getFormUpladObj = function () {
      var str = '<input name="file" type="file" class="ui-file"/>'; // 表单上传
      return $(str).appendTo($("body"));
    };
    
    Function.prototype.after = function (fn) {
      var self = this;
      return function () {
        var ret = self.apply(this, arguments);
        if (!ret) {
          return fn.apply(this, arguments);
        }
        return ret;
      };
    };
    
    var uploadObj = iteratorUploadObj(
      getActiveUploadObj,
      getFlashUploadObj,
      getFormUpladObj
    );
    

    其中迭代器可以这样去实现

    // 迭代器
    var iteratorUploadObj = function () {
      for (var i = 0, fn; (fn = arguments[i++]); ) {
        var uploadObj = fn();
        if (uploadObj !== false) {
          return uploadObj;
        }
      }
    };
    

    6.3 职责链模式

    定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间 的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

    【总结】代码优化篇

    我们利用after去实现职责链

    Function.prototype.after = function (fn) {
      var _this = this;
      return function () {
        var ret = _this.apply(this.arguments);
        fn.apply(this, arguments);
        return ret;
      };
    };
    

    同样是上面那个文件上传的例子,用迭代器模式这样实现

    // 职责链模式
    var getActiveUploadObj = function () {
      try {
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
      } catch (e) {
        return "nextSuccessor";
      }
    };
    
    var getFlashUploadObj = function () {
      if (supportFlash()) {
        var str = '<object type="application/x-shockwave-flash"></object>';
    
        return $(str).appendTo($("body"));
      }
    
      return "nextSuccessor";
    };
    
    var getFormUpladObj = function () {
      return $('<form><input name="file" type="file"/></form>').appendTo($("body"));
    };
    
    var getUploadObj = getActiveUploadObj
      .after(getFlashUploadObj)
      .after(getFormUpladObj);
    
    console.log(getUploadObj());
    

    6.4 状态模式

    定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

    Light.prototype.buttonWasPressed = function () {
      if (this.state === "off") {
        console.log("弱光");
        this.state = "weakLight";
      } else if (this.state === "weakLight") {
        console.log("强光");
        this.state = "strongLight";
      } else if (this.state === "strongLight") {
        console.log("关灯");
        this.state = "off";
      }
    };
    
    // 状态模式
    var Light = function () {
      this.currState = FSM.off; // 设置当前状态
      this.button = null;
    };
    Light.prototype.press = function () {
      this.currState.buttonWasPressed.call(self); // 把请求委托给 FSM 状态机
    };
    var FSM = {
      off: {
        buttonWasPressed: function () {
          console.log("弱光");
          this.currState = "weakLight";
        },
      },
      weakLight: {
        buttonWasPressed: function () {
          console.log("强光");
          this.currState = "strongLight";
        },
      },
      strongLight: {
        buttonWasPressed: function () {
          console.log("关灯");
          this.currState = "off";
        },
      },
    };
    var light = new Light();
    light.press();
    

    四、团队需要完善的地方

    1. 团队编码规范
    2. eslint统一规范
    3. 定期code review

    五、参考

    一名合格前端工程师必备素质:代码整洁之道

    你所需要知道的代码整洁之道

    JavaScript轻量级函数式编程


    下载网 » 【总结】代码优化篇

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元