面试:性能优化表格和业务场景优化,你了解吗?

前言

3月的第一个开头,失业已久的我参加了一次线下面试,很有意思的是,这一次面试直接是开发+项目经理+HR,一轮到底。

具体结果暂不知道,但是我的回答确实一般般。总而言之,言而总之,面试复盘还是不能少,起码这一次知道了自己还有很多不足可以改进。

业务

表格获取大数据量,如何渲染

这个问题我首先答的是分页,毕竟这个最简单后端查一下表即可,也是业务中最常用的。

即使后端没有做分页,前端做分页也不是不行(除非万不得已,我从不前端分)。

PS:之前在重构时,确实因为时间关系,没有改造一处老代码,所以使用了前端分页。同时联合左树右穿梭框,这个组件的逻辑相当感人,改一处就是一个bug。

同时在前端直接渲染的情况下,我们肯定是可以用到虚拟滚动的,目前业内大部分组件库都有。

但是在查找了众多资料之后,发现似乎对于这块没有太好的业务处理办法?

  1. 使用虚拟滚动:当数据量非常大时,可以使用虚拟滚动的方式来进行渲染,即只渲染可视区域的数据。这样可以大大减少DOM节点的数量,提高性能。
  2. 使用分页:可以将数据进行分页处理,每次只加载一部分数据,这样可以提高渲染性能。
  3. 使用Object.freeze() :在Vue中,所有的数据都是响应式的,Vue会为每一个数据对象创建getter和setter,这在数据量大的时候会造成性能问题。如果你的数据不需要响应式,可以使用Object.freeze()来冻结数据,防止Vue将其转换为响应式。例如:
javascript 复制代码
this.list = Object.freeze(list);

这时,说到虚拟滚动,面试官就来劲了!

表格中合并单元格因为虚拟滚动导致显示不全如何解决?

问:在使用虚拟滚动的表格中,合并单元格可能会导致显示问题。这是因为虚拟滚动的实现方式是只渲染可视区域的数据,而合并的单元格可能会跨越多个可视区域,从而导致显示不全的问题。

啊?我真没用过合并单元格。。。硬着头皮聊吧。

我个人认为这块要不就不用,要不就告诉表格,哪块需要联合渲染,从而不出现异常。

然后我回来了解了一下,也找到了一个github的issue,但感觉不太像是面试官想要的正确答案,有了解的大佬评论区说一下。

  1. 调整滚动策略:尝试调整滚动的实现方式,使其能够更好地处理合并的单元格。例如,你可以尝试在滚动时保持合并的单元格完全可见。

  2. 动态调整单元格大小:在滚动时动态调整合并单元格的大小,使其始终适应可视区域。

  3. 避免在使用虚拟滚动的表格中合并单元格:如果可能,你可以考虑避免在使用虚拟滚动的表格中合并单元格。虽然这可能会影响到表格的布局和视觉效果,但它可以避免由于单元格合并而导致的显示问题。

  4. 使用其他的表格库或工具:如果你的表格库或工具无法很好地处理这个问题,你可以考虑使用其他的表格库或工具。例如,有些表格库可能已经解决了这个问题,或者提供了更多的选项来处理合并单元格和虚拟滚动。

合并单元格并且冻结列 在虚拟滚动时不能共存 · Issue #970 · x-extends/vxe-table · GitHub.

前端如何在本地实现大数据量搜索,例如搜索1w条数据中name为gis?

上一个问题回答的不是特别好,而面对这一个问题,我也紧张兮兮的,大概只答出了使用map和原生方案。

其余方案我觉得比较好的可能是用模糊搜索算法,都了解一下吧:

  • 分组插入数据:如果数据量太大,您可以将数据分组插入页面。例如,一次性插入100条数据,然后根据用户的搜索条件进行过滤和展示。

  • 使用索引 :如果您的数据是静态的,可以在加载数据时创建一个索引,以便快速搜索。例如,将name字段作为索引,存储在一个对象中,然后根据用户输入的关键词在索引中查找匹配项。

  • 使用Web Workers:如果您需要在主线程之外进行搜索操作,可以使用Web Workers。Web Workers是在后台运行的独立线程,不会阻塞主线程的执行。

  • 本地存储 :如果数据量不是很大,您可以将数据存储在本地,例如使用localStorageIndexedDB。然后在本地进行搜索操作。

如果是用数组原生方案,以下是一个简单的示例:

vue 复制代码
<template>
  <div>
    <input v-model="searchKeyword" @input="handleSearch" placeholder="Search...">
    <ul>
      <li v-for="item in filteredData" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: [], // 假设这里是1万条数据
      searchKeyword: ""
    };
  },
  computed: {
    filteredData() {
      // 根据关键词过滤数据
      return this.data.filter(item => item.name.includes(this.searchKeyword));
    }
  },
  methods: {
    handleSearch() {
      // 处理搜索逻辑
      // 可以在这里使用防抖函数
    }
  }
};
</script>

canvas在vue里面怎么使用?

在Vue.js中使用Canvas可以通过以下步骤进行:

  1. 在Vue组件中引入Canvas元素,可以使用<canvas></canvas>标签或者通过<div>元素动态创建Canvas。
  2. 在Vue组件的mounted生命周期钩子函数中获取Canvas元素的上下文。您可以使用document.getElementById('canvasId')或者通过ref属性获取元素。

以下是一个简单的Vue组件示例,展示了如何在Vue中使用Canvas:

vue 复制代码
<template>
  <div>
    <!-- 使用ref属性获取Canvas元素 -->
    <canvas ref="myCanvas"></canvas>
  </div>
</template>

<script>
export default {
  mounted() {
    this.initCanvas();

    // 当调整窗口大小时重绘canvas
    window.onresize = () => {
      this.initCanvas();
    };
  },
  methods: {
    initCanvas() {
      console.log("初始化canvas");
      const canvas = this.$refs.myCanvas;
      const ctx = canvas.getContext("2d");

      // 设置Canvas宽高为窗口大小
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;

      // 绘制Canvas内容
      this.drawCanvas(ctx);
    },
    drawCanvas(ctx) {
      // 在这里编写您的Canvas绘制逻辑
      ctx.beginPath();
      ctx.moveTo(20, 20);
      ctx.lineTo(140, 70);
      ctx.lineWidth = 1.0;
      ctx.strokeStyle = "#cc0000";
      ctx.stroke();
    }
  }
};
</script>

vuex和pinia的区别,pinia为什么要删除vuex的mutations方法?

Vuex 和 Pinia 的区别

  1. 数据结构

    • Vuex :使用模块化的方式来组织状态,包括 statemutationsactionsgetters
    • Pinia :更简洁,只有 stateactionsgetters,没有 mutations
  2. 同步和异步操作

    • Vuex :支持同步和异步的 mutationsactions
    • Pinia :只有异步的 actions,没有同步的 mutations。这是因为同步的 mutations 可能导致状态变更不可追踪,而异步的 actions 更容易进行状态管理。
  3. 体积和性能

    • Vuex:相对较大,适用于大型、复杂的应用程序。
    • Pinia:非常轻量级(体积约 1KB),适用于中小型应用。

pinia为什么要删除vuex的mutations方法

其实我还是赞同大部分人了解的,pinia的actions同步异步都支持,而且mutation确实冗余,我们直接上官方图:

composition-api函数式编程和面向对象编程的不同。

  1. 数据处理方式

    • 函数式编程:强调使用纯函数(Pure Functions),即输入相同,输出也相同,没有副作用。它关注数据的转换和处理,避免共享状态和可变数据。
    • 面向对象编程:将数据和操作封装在对象中,通过方法调用来处理数据。它关注对象之间的交互和继承。
  2. 数据不可变性

    • 函数式编程:鼓励使用不可变数据,避免直接修改原始数据。
    • 面向对象编程:通常使用可变数据,通过方法来改变对象的状态。
  3. 代码组织方式

    • 函数式编程:更加模块化,强调函数的组合和复用。函数是一等公民,可以作为参数传递和返回值。
    • 面向对象编程:更加面向业务逻辑,强调对象之间的关系和继承。类和对象是核心概念。
  4. 状态管理

    • 函数式编程:通常使用不可变数据结构来管理状态,例如Redux中的不可变状态树。
    • 面向对象编程:使用类和对象来管理状态,例如Vue中的响应式数据。
  5. 代码风格

    • 函数式编程:更加函数式,注重表达式和纯函数的组合。
    • 面向对象编程:更加面向对象,注重类、继承和方法的调用。

总之,两者并不是对立的,而是可以结合使用。在现代前端开发中,Vue 3的Composition API借鉴了函数式编程的思想,提供了更灵活的组合方式,同时也保留了面向对象编程的特性。选择适合项目需求的编程范式是关键,而不是死板地追求某一种方式。

我认为这两篇文章也写的很好,分享:

PC端的Vue中如何实现移动端的onload函数,仅在第一次加载时执行一次,之后不再执行?(例如:单点登录后加载,但是重复刷新页面后不加载)

emmm,说实话,这块我也是在已读乱答,了解到需求之后,我回答的逻辑是

  1. 放在created生命周期里面
  2. 标识放在Vuex或者pinia里面做全局变量

但面试官说,要刷新之后也知道是第2次进来,我就说放在localstorage里面。

其实回来之后我又想到为啥不写到cookie,后端在读到这个cookie的时候就下发一个字段,前端识别到之后不再处理逻辑。

总之就是做持久化保存,不知道大家认为对不对?

我看网上的一些答案,也差不多呀。。。

  1. 使用localStoragesessionStorage :在onload函数中,检查是否已经执行过。如果已经执行过,将一个标志位存储在localStoragesessionStorage中,以便后续判断。例如:
javascript 复制代码
export default {
    data() {
    return {
        hasLoaded: false
    };
    },
    mounted() {
    if (!this.hasLoaded) {
        // 执行初始化操作
        this.doInitialLoad();
        this.hasLoaded = true;
    }
    },
    methods: {
    doInitialLoad() {
        // 在这里执行只需在第一次加载时执行的操作
    }
    }
};
  1. 使用beforeCreate钩子 :将只需在第一次加载时执行的操作放在beforeCreate钩子中。这样,每次组件被创建时都会执行,但只有在第一次加载时才会执行初始化操作。
javascript 复制代码
export default {
    beforeCreate() {
    // 在这里执行只需在第一次加载时执行的操作
    this.doInitialLoad();
    },
    methods: {
    doInitialLoad() {
        // 在这里执行只需在第一次加载时执行的操作
    }
    }
};
  1. 使用created钩子和标志位 :在created钩子中执行初始化操作,并使用一个标志位来判断是否已经执行过。例如:
javascript 复制代码
export default {
    data() {
    return {
        hasLoaded: false
    };
    },
    created() {
    if (!this.hasLoaded) {
        // 执行初始化操作
        this.doInitialLoad();
        this.hasLoaded = true;
    }
    },
    methods: {
    doInitialLoad() {
        // 在这里执行只需在第一次加载时执行的操作
    }
    }
};

如何实现表格中的树已经加载了,但是在刷新页面之后仍然展示上一次加载的数据?

  1. 本地存储(LocalStorage 或 SessionStorage):将状态存储在本地浏览器中。当页面刷新时,您可以从本地存储中获取状态并还原应用程序的状态。请注意,本地存储的数据会一直保留,直到用户手动清除或浏览器缓存过期。

  2. 服务端存储:将状态存储在服务器上,而不是仅存在于前端内存中。当页面刷新时,您可以从服务器获取状态并重新加载应用程序的状态。这需要与后端进行交互,但可以确保状态的持久性。

  3. URL 参数:将状态信息作为 URL 参数传递。当页面刷新时,您可以从 URL 参数中获取数据并还原应用程序的状态。

  4. Cookies:将状态信息存储在 Cookie 中。这样,即使页面刷新,您也可以从 Cookie 中读取数据并还原应用程序的状态。

G6大数据量如何优化

这块没得说,G6底层源码确实写的也一般,我常用的就是分步渲染,丢到下一个宏任务里去。不过看到网上说,能够局部刷新和Web Workers时,我感觉我新世界大门又打开了。

  1. 分层渲染:将图形数据分层,只渲染当前视窗内的数据,而不是一次性渲染所有节点和边。这可以减少渲染的工作量。

  2. 虚拟化:只渲染可见区域内的节点和边,而不是整个图。这可以通过设置视窗大小和缩放级别来实现。

  3. 局部刷新:仅重新渲染发生变化的部分,而不是整个图。例如,当用户拖动或缩放图时,只更新可见区域内的节点和边。

  4. 使用Web Workers:将图形渲染操作放在Web Workers中,以便在后台线程中执行,不会阻塞主线程。

  5. GPU加速:使用支持GPU加速的布局算法,例如Fruchterman布局。在大规模数据时,GPU布局的性能通常更好。

  6. 数据聚合:对于大量数据,可以考虑数据聚合,例如将多个节点合并成一个聚合节点,以减少渲染的节点数量。

  7. 性能测试和分析:使用Chrome DevTools的Performance面板来分析性能瓶颈,并查看哪些操作占用了大部分时间。

为此我找了一些关于G6渲染大数据量的优化方案给大家详细了解:

  1. 关于大数据量性能优化请教 · Issue #4333 · antvis/G6 · GitHub

  2. 如何定位解决前端大数据量渲染下的性能问题

  3. G6展示网络拓扑图,适配大数据量的方案

  4. AntV G6 的坑之------从卡掉渣到满帧需要几步

项目管理:

敏捷项目中使用的项目管理工具

我确实在23年3月考完PMP之后,很长时间没有再将PMP的知识点进行复习了。

像其他的管理方案,敏捷项目管理也分为不同的阶段,例如:

  1. Scrum:Scrum 是一种流行的敏捷方法,强调团队协作、迭代开发和持续交付。它包括角色(Scrum Master、Product Owner、开发团队)、仪式(Sprint Planning、Daily Standup、Sprint Review、Sprint Retrospective)和工件(Product Backlog、Sprint Backlog、Increment)。 详细请看:What is Scrum? | Scrum.org.
  2. 看板:看板是一种敏捷项目管理工具,它使用可视化任务板来管理项目。每个任务都有一个状态标签,表示任务的当前状态。看板可以帮助团队可视化任务的状态,并推动团队完成任务。
  3. FDD(特性驱动开发)和TDD(测试驱动开发),将开发过程分解为小的功能特性或者测试点,从而达到逐步完成的目的,它们也都是很好的工具。
  4. 甚至还有XP(极限编程):XP 是一种软件开发方法,强调测试驱动开发、持续集成、结对编程、简单设计和迭代开发。它适用于高度协作的团队。
  5. 或者更加离谱的"日不落编程",指的是东半球的程序员开发完成之后,交给西半球的程序员,从而达到这个系统每时每刻都在开发。

算法

长数字转财务格式的算法

说实话算法这一块我实在是没有刷过多少题,斐波那契数列算是我的极限水平(经典算法:爬楼梯,入室抢劫)。

比大部分掘金的大佬都菜。

所以我大致只想到能够转为字符串或者转为数组,但是面试官强烈要求说一些算法,我比较懵,如果有懂的大佬,可以告诉我一下。

我想到的方案如下:

  1. 添加千位分隔符

    • 将数字转换为字符串。
    • 从右向左遍历字符串,每隔三位插入一个逗号(或其他千位分隔符)。
  2. 保留小数位数

    • 如果您需要保留小数位数,可以使用四舍五入或截断的方式。
    • 将数字转换为字符串。
    • 使用适当的方法来处理小数位数,例如使用 toFixed() 函数。

以下是一个示例的JavaScript代码,演示了如何将长数字转换为财务格式:

javascript 复制代码
function formatNumberToFinance(number, decimalPlaces) {
  // 将数字转换为字符串
  const numStr = number.toString();

  // 添加千位分隔符
  let formattedStr = '';
  let count = 0;
  for (let i = numStr.length - 1; i >= 0; i--) {
    formattedStr = numStr[i] + formattedStr;
    count++;
    if (count % 3 === 0 && i > 0) {
      formattedStr = ',' + formattedStr;
    }
  }

  // 处理小数位数
  if (decimalPlaces > 0) {
    formattedStr = parseFloat(formattedStr).toFixed(decimalPlaces);
  }

  return formattedStr;
}

// 示例
const longNumber = 1234567890.1234567;
const financeFormat = formatNumberToFinance(longNumber, 2);
console.log(financeFormat); // 输出:1,234,567,890.12

总结

这次面试还是发现自己的日常总结不到位,经常使用的方案可以再复习一下。

还有算法要刷了,哭死!

还有CSS,这块我想是大部分前端开发的痛点,我也得复习一下了(虽然这次没问)。

相关推荐
恋猫de小郭11 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端