前端性能优化之重构方案和计划 - zptime/blog GitHub Wiki

随着项目的敏捷开发快速迭代,相似的代码越来越多,开发时可能为了赶进度,没那么多时间考虑代码质量,业务流程也随着迭代不断扩展,开发时没法考虑的那么全面。最终,前端项目将会愈发冗余,且存在诸多历史遗留代码或者问题。所以隔一段时间,进行适度的代码重构是很必要的,可提高代码的可读性和代码质量。但重构也不是说必须一下全部搞完,也需要考虑重构的代价,特别是一些运行很久的代码,里面肯定有很多踩坑的过程,要综合考虑重构是否值得以及重构的范围。对于自己完全熟悉的代码,也要一步一步来,避免影响正常的项目迭代。

业务逻辑重构

主要是优化代码结构,提高可读性;进行合理的拆分和组合,提高代码复用性

  1. 排查并去除程序中的魔鬼数字,将数字定义为具有实际意义的宏常量
  2. 排查并使用卫语句解除 if 语句的多层嵌套
  3. 排查并删除冗余的业务逻辑
  4. 排查并合并重复业务代码,抽离出公共函数
  5. 排查业务逻辑和底层工具类控件类是否存在互相依赖耦合
  6. 排查并将后台提供的网络接口封装为 API,实现前端业务界面和后端 API 分离
  7. 排查过长大函数,将大函数分成若干独立功能小函数,避免一个函数实现过多功能,遵循“单一职责原则”
  8. 用多态替换条件判断

以下函数举例,存在上述中的 1,2 两种情况

// 改造前
// (1) 1,2,3等都是魔鬼数字
// (2) 存在if/else多层嵌套
function doSomething() {
  if (this.type === 1) {
    // 编辑部分数据
    // todo...
  } else {
    // todo...
    let msg = "";
    if (this.type === 2) {
      msg = "编辑成功";
      // todo...
    } else if (this.type === 3) {
      msg = "新增成功";
      // todo
    }
    // todo...
  }
}

// 改造后
const ADD_FLAG = 1;
const EDIT_FLAG = 2;
const UPDATE_FLAG = 3;
function doSomething() {
  switch (this.type) {
    case "UPDATE_FLAG":
      // todo
      break;
    case "EDIT_FLAG":
      let msg = "编辑成功";
    // todo
    case "ADD_FLAG":
      let msg = "新增成功";
    // todo
    default:
      // todo
      break;
  }
}

以下函数举例,存在上述中的 7 的情况

// 改造前
function submitForm() {
  if (this.type === "part") {
    // 编辑部分数据
    let params = {
      id: this.id,
      memberIds: R.map((o) => o.id, this.members),
    };
    Api["modifyPartConfig"](params).then((res) => {
      this.$notification.success({
        class: "m-notice",
        message: "配置成功",
      });
    });
  } else {
    // 编辑全量数据
    this.$refs["formRule"].submitForm((data) => {
      if (!data) return;

      let params = R.mergeDeepRight(this.formInfo, data);
      params.memberIds = R.map((o) => o.id, this.members);

      let msg = "";
      if (this.type === "edit") {
        params["id"] = this.id;
        msg = "编辑成功";
      } else {
        msg = "新增成功";
        params.configRequest = R.clone(params.config);
      }
      Api["modifyConfig"](params).then((res) => {
        this.$notification.success({
          class: "m-notice",
          message: msg,
        });
      });
    });
  }
}

// 改造后
function submitForm() {
  if (this.type === "part") {
    // 编辑部分数据
    let params = {
      id: this.id,
      memberIds: R.map((o) => o.id, this.members),
    };
    Api["modifyPartConfig"](params).then((res) => {
      this.$notification.success({
        class: "m-notice",
        message: "配置成功",
      });
    });
    return false;
  }

  this.handleEditSubmit();
}

function handleEditSubmit() {
  // 编辑全量数据
  this.$refs["formRule"].submitForm((data) => {
    if (!data) return;

    let params = R.mergeDeepRight(this.formInfo, data);
    params.memberIds = R.map((o) => o.id, this.members);

    let msg = "";
    if (this.type === "edit") {
      params.id = this.id;
      msg = "编辑成功";
    } else {
      msg = "新增成功";
      params.configRequest = R.clone(params.config);
    }
    Api["modifyConfig"](params).then((res) => {
      this.$notification.success({
        class: "m-notice",
        message: msg,
      });
    });
  });
}

平台通用组件重构

  1. 通用组件的提取和封装
  2. 组件插件化开发和远程部署
  3. 排查通用组件和业务代码是否存在相互耦合,存在问题需要解耦

系统稳定性重构

  1. 排查常见错误
  • Error:错误的基类,其他错误都继承自该类型
  • EvalError:Eval 函数执行异常
  • RangeError: 数组越界
  • ReferenceError: 引用类型错误,引用不存在的变量时发生的错误
  • SyntaxError:语法错误
  • TypeError:类型错误,表示值的类型非预期类型时发生的错误
  • URIError:以一种错误的方式使用全局 URI 处理函数而产生的错误
  1. 异常捕获加日志

内存泄漏排查

  1. 定时器
  2. 静态变量
  3. 意外的全局变量:排查 windows 上挂载的对象,在使用完需要释放
  4. 闭包引起的内存泄漏
  5. 没有清理的 DOM 元素引用
  6. 事件监听,没有解绑

无用资源删除

无用的资源,例如图片,css 样式,JS 等删除

性能部分重构:

  1. 排查并识别提高网站性能的优化点
  2. 排查和删除无用的界面层次,减少界面绘制时间
  3. 排查长代码页面,进行合理的模块化改造
  4. 公共样式代码抽取,css 预编译语言的合理使用,scoped 的合理使用