elementui弹窗列表-大厂采访者:你做过什么亮点项目吗?

前言

大场笔试,不仅问常见的算法网络基础,还有一些八股的笔迹,但经常出现的一个问题是,你做过什么项目吗?

很多朋友不知道如何回答看似简单的问题,因为笔试就像相亲一样。 你喜欢的女孩不想听你说你是高二的三年级学生。 她似乎只想知道你今天有什么特点和优点。

对于项目来说也是如此。 虽然面试官想看你做过什么亮点项目,但其实你日常的项目都差不多,比如增、删、改、查、登录、注册、弹窗等等,所谓的亮点都是通过这种方式实现的。 在功能的基础上,我们进行了以下几方面的探索和优化。 由于我个人能力有限,我先谈谈这几个方面。

大数据量优化

研发效率提升

研发质量提升

性能优化

用户体验优化

复杂的新场景

....

我们以大家做过的需求为例。 通过优化,可以将每一个需求变成一个亮点需求,也就是所谓的企业级项目。

欢迎加我聊后端和英语学习

数据量大

想要做出亮点,首先要做一些身边同学满足不了的需求。 首先,数据量变得越来越大。 虽然大部分场景都没有遇到过,但是却挡不住喜欢提问的无聊面试官。 我们只谈论笔试。

场景一:课程页面,增删改查

在这种场景下,我们可以将数据量做成1W行。 大部分场景都是分页,只有极端场景(移动端无限滚动的产品页面)。 如果直接渲染1W行的列表,不出意外你的页面就会卡住。 是的,比较常见的优化方案就是虚拟滚动,就是在窗口中只渲染你能看到的几十行,然后通过监听滚动来更新这几十行dom。 大致原理图如下(网上找的)

解决方案下来之后,Vue 和 React 的解决方案都是类似的。 这里我们用React+Typescript来举个栗子。 首先,我们要完成以下任务。 为了简化场景,首先假设每个元素的高度相同

可视区域的高度固定viewHeight(clientHeight

每个列表高度高度(固定

可视区域的数据索引起始和结束(scrollTop/height

根据startIndex估计偏移倾斜(scrollTop - (scrollTop % height);

渲染数据并监控滚动风暴

代码大致如下

  // 列表容器的dom
  const container = useRef(null)
  // 开始位置
  const [start, setStart] = useState(0)
  // 视图中的数据
  const [visibleData, setVisibleData] = useState<VirtualProps['list']>([])
  // 控制偏移量
  const [viewTransfrom, setViewTransfrom] = useState('translate3d(0,0,0)')
  useEffect(() => {
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度
    const visibleCount = Math.ceil(viewHeight / HEIGHT) // 视窗内有几个元素
    const end = start + visibleCount
    setVisibleData(list.slice(start, end))
  }, [])
  function handleScroll(e: React.UIEvent{
    const scrollTop = e.currentTarget.scrollTop // 滚动的距离
    const containerDom = container.current
    const viewHeight = containerDom?.clientHeight || 500 // 视窗高度

    const start = Math.floor(scrollTop / HEIGHT)
    const end = start + Math.ceil(viewHeight / HEIGHT)
    setVisibleData(list.slice(start, end))
    setStart(start)
    setViewTransfrom(`translate3d(0,${start * HEIGHT}px,0)`)
  }

稍微困难一点或者比较无聊的面试官会问,如果每一行都是一段文字,那么高度会是多少?其实解决办法并不复杂。 可以估算出一个大概的高度,然后在渲染的时候得到实际的dom高度+缓存到链表中。

以下是伪代码。 由于位置链表是一个累加字段,所以实际上可以使用二值算法继续优化。 给你的朋友留一份作业。

// 预估高度60
const PREDICT_HEIGHT = 60
  // 不定高数组,维护一个位置数据
const [positions, setPosition] = useState<{ top: number;height: number }[]>([])

// 渲染数组之后,更新positions数组
Array.from(listDom?.children).forEach((node, index) => {
  const { height } = node.getBoundingClientRect()
  // console.log(start+index, node.id)
  if (height !== positions[start + index].height) {
    setPosition((prev) => {
      const newPos = [...prev]
      newPos[start + index].height = height
      for (let k = index + 1; k < prev.length; k++)
        newPos[k].top = newPos[k - 1].top + newPos[k - 1].height
      return newPos
    })
  }
})
}, [visibleData])

06.gif场景2:文件上传

字节跳动面试官elementui弹窗列表,我也实现了上传大文件的场景。 我之前写过一篇文章,在这里回顾一下。 其实数据量大了之后,想要继续让用户有更好的交互体验,就得不断解决新的问题

上传普通文件 axios.post + 进度条就完成了。 如果想要有亮点,可以将文件大小调大一些,比如2G。 直接上传很容易中断。 我们需要在断点处恢复上传,并且几个新的诞生了。 问题

文件剪切+秒传+暂停

文件的估计哈希值,就像文件的ID号一样elementui弹窗列表,用于询问前端是否有块。

针对哈希计算滞后的解决方案有3种:web-worker、时间分片、采样Hash。

将文件上传到块中

完成上面的解决方案后,如果面试官问到文件上传的需求,我想聊半个小时应该没问题吧,web-worker,从React源码学来的时间分片,基于布隆过滤器思想的哈希采样、TCP的慢启动理想、字节高频面试题、异步任务并发控制项目实践

图片.png

图片.png

web-worker估计md5(影子克隆策略)

图片.png

在React16之前的架构中,存在一个性能困境,即当virtual dom的diff时间过长时,可能会造成lag。 React16使用了Fiber,这是一种时间阻塞架构来解决这个问题。 现在估计ms5也会类似。 在场景中,我们也可以从过度计算带来的滞后中得到教训。

图片.png

图片.png

md5计算也完成了。 如果有100个图块,直接上传到Promise.all,同时发起100个请求会导致浏览器卡顿。 我们需要控制并发异步任务的数量,这是字节跳动的一个常见问题。 话题

图片.png

你可以参考我的Github这里,可以利用队列和Promise.race的特性来实现。


function limit(maxCount){
  // 任务队列
  let queue = []
  let activeCount = 0

  const next = ()=>{
    //下一个任务
    activeCount--
    if(queue.length>0){
      queue.shift()()
    }
  }
  const run = async (fn,resolve,args)=>{
    //执行一个函数
    activeCount++
    const result = (async()=>fn(...args))()
    resolve(result)
    await result
    next() //下一个
  }
  const push = async (fn,resolve,args)=>{
    queue.push(run.bind(null,fn,resolve,args))
    if(activeCount0){
      // 队列没满 并且还有任务 启动任务
      queue.shift()()
    }
  }

  let runner = (fn,...args)=>{
    return new Promise((resolve)=>{
      push(fn,resolve,args)
    })
  }
  return runner
}

也可以使用Promise来实现,仅供参考

async function asyncPool({
  limit,
  items,
  fn
}
{
  const promises= []
  const pool = new Set()
  for (const item of items) {
    const promise = fn(item)
    promises.push(promise)
    pool.add(promise)
    const clean = () => pool.delete(promise)
    promise.then(clean, clean)
    if (pool.size >= limit) await Promise.race(pool)
  }
  return Promise.all(promises)
}

总结一下,对于你现在做的大部分需求,你只需要考虑大数据量,然后逐步解决大数据量带来的性能问题,这就是亮点之一。

研发效率提升

程序员也是一种非常昂贵的资源,可以提高他们的开发效率,节省公司资金。 当然是亮点,但是每个人的开发能力不一样。 我们可以通过团队协作和多个项目重用它们。 提高研发效率的两种途径

团队效率

最常见的是统一规范、js规范、git分支规范、日志规范、项目文件规范,并使用合适的工具进行自动化校准和修正。

然后就是多个项目之间的复用率,也可以大大提高效率。

代码初始化可以封装成脚手架,类似于create-vite,并且可以外部化上面提到的各种规范,直接启动新项目。

代码开发效率,前端主要是组件库和工具库utils包

代码联调效率,如手动从socket json生成Typescript套接字类型等,如mock数据工具

代码在线效率、发布部署、部署结果同步到聊天群等,自动化日常重复行为

这部分也有大量开源代码可供参考,比如React生态中的AntDesign、Vue生态中的Antd-vue和element-ui、vueuse、通用工具库参考lodash等,在此我不再赘述。

这也是大多数团队有机会做开源的领域。 因为需要多个项目之间共享,所以对代码质量、版本管理、代码文档都会有更高的要求,无形中提升了我们的排名和能力

现在流行的rust生态可以大大提高后端编译的速度。 事实上,这也无形中提高了开发人员编写代码时的心情和效率。 比如webpack被vite替代,babel被swc替代,现在很流行。 rspack力求让后端开发环境拥有流畅、即时的开启体验。

还有一些非代码级别的协作效率,例如敏捷看板、高效会议、代码审查等,这些都超出了本文的范围,暂时跳过。

研发品质提升

质量的提高也提高了程序员的上限。

这部分虽然也是一个很大的话题,但是《重构》、《整洁代码的艺术》、《代码百科》等经典书籍不计其数。 然而在后端这个比较狂野的领域,能够做好自动化测试已经是非常难得了。

业务页面编写测试的成本比较高,但是多个项目之间共享的组件库、工具库还是需要通过测试来保证代码质量。 学习用 jest 或 vitest 编写测试也让我们有机会参与流行的开源项目。 机会,代码测试覆盖率也是项目质量的重要指标,也是代码可维护性高的体现。

现在您可以尝试使用 vitest 为您的项目编写工具函数或组件库来保护您的测试代码。

除了代码级单元测试之外,还有流程级测试,例如代码审查。

性能优化

用户界面

如何让页面打开速度更快是一个永恒的话题。 性能优化第一课,首先要了解页面性能的几个常见指标,FCP、TTI、LCP。 就像我们想要提高游戏水平就必须了解攻击力一样。 防御力那些参数的含义

图片.png

那么后端优化可以从两个方向入手

1. 更快的文件加载

首先,后端工程中的打包压缩是为了减少文件的体积和数量,并且通过良好的文件缓存管理,可以最大限度的利用浏览器缓存,达到更快加载文件的目的

就文件大小而言,其实图片的格式选择和优化才是最重要的。 jpg、png、webp的选择,以及打包时图片的压缩,都可以获得可观的批量利润。 对于静态资源,还可以利用CDN来继续增强文件的加载。 速度

还可以利用延迟加载的思想来减少首屏加载的文件数量,同时还可以提高文件加载的速度。 图片/路由延迟加载现在是后端开发领域必备的功能。 赶紧写个lazy-load的

rollup带来的tree-shaking能力(类似互联网公司裁员,尴尬),可以去除项目中无用的代码,现在已经成为后端工程的标配,这就是为什么我们要尽力的原因向ESM规范靠拢是原因之一,而且静态分析还可以给我们带来一些额外的利润,比如在vite中预先打包等。

我们在工程过程中都会用到工具或者插件,所以学习定制webpack/vite插件就成为了后端架构师必备的能力之一。 快去学学吧

2. 代码执行速度更快

这里也很容易理解。 执行速度也是鉴定代码质量的一个指标。 例如,相同的leftpad功能(前面的字符),如果这样写

function leftpad(str,length,ch){
  let len = length-str.length+1
  return Array(len).join(ch)+str
}
console.log(leftpad('hello',10,'0'))

对比我们用二进制法+位运算优化思想来写的方式

function leftpad2(str,length,ch){
  let len = length-str.length
  total = ''
  while(true){
    // if(len%2==1){
    if(len & 1){
      total+=ch
    }
    if(len==1){
      return total+str
    }
    ch += ch
    len = len >> 1
    // len = parseInt(len/2)
  }
}
console.log(leftpad2('hello',10,'0'))

console.time('leftpad')
for(let i=0;i<10000;i++){
  leftpad('hello',1000,'0')
}
console.timeEnd('leftpad')

console.time('leftpad2')
for(let i=0;i<10000;i++){
  leftpad2('hello',1000,'0')
}
console.timeEnd('leftpad2')

❯ node leftpad.js
00000hello
00000hello
leftpad51.97ms
leftpad22.077ms

数据量越大,性能差异就越大。 当数据量为1W时,性能相差25倍。 当然,你也可以看看后端算法和数据结构的必要性,针对不同的场景选择合适的算法或者数据结构,这也是中级后端必备的能力

不同的框架也有不同的性能优化方法,比如减少组件不必要的渲染、减少浏览器的重绘回流、减少页面内部的DOM操作等等。

还有按需执行代码的思路,比如vue3的静态标记,只有dom的动态部分需要参与diff的估计,静态的dom会直接跳过,孤岛astro和nuxt3的ssr框架中的结构是只激活页面中动态组件的js,是按需思维的体现。

复杂场景

一些本来就复杂的场景,或者后端的新兴领域或者热门领域,面试官也比较喜欢,这个方向的比较多,以后有机会再详细讨论

非常流行的低代码(搭建平台

文档技术(在线办公、通知笔记

图形(figma、白板

3D(可视化、游戏、webgl