Skip to content
目录

3 月 13 号面试题(数字马力 一面)

1. 自我介绍

2. 毕业时间

3. 在团队中主要扮演什么样的角色?主要负责什么内容?

4. 分享一下在前端框架搭建中学习到的知识?你觉得学到最有用的东西是什么?

  • 模块化与依赖管理:

学习如何使用 CommonJS、ES6 Modules 或其他模块系统组织代码结构,以及如何通过 npm 或 yarn 进行依赖包的管理和安装。 理解 Webpack、Rollup 等构建工具的工作原理,如何配置它们来进行模块打包、资源加载、代码分割和优化。

  • 框架核心概念:

例如在 React 中,理解虚拟 DOM、组件化开发思想、props 与 state 的管理机制,以及生命周期方法的应用。 在 Vue.js 中,掌握声明式渲染、响应式数据绑定、组件化开发体系以及指令、插槽、计算属性等特性。 Angular 则强调依赖注入、模板语法、组件通信以及变更检测机制等核心概念。

  • 状态管理:

Redux、MobX、Vuex 等库的学习,了解如何集中管理和处理全局应用状态,以及如何设计良好的数据流架构。

  • 路由系统:

学习如何使用 React Router、Vue Router 等路由库进行页面间的跳转及参数传递,以及实现动态路由和懒加载等功能。

  • 性能优化:

如何提高页面加载速度,如静态资源压缩、CDN 加速、服务端渲染(SSR)、预渲染(prerendering)等技术。 性能监控与调试技巧,如使用浏览器开发者工具分析性能瓶颈、识别冗余渲染等问题。

  • 最佳实践与工程化:

持续集成与部署(CI/CD),如使用 GitHub Actions、Jenkins 等工具自动化构建流程。 遵循代码规范和编写高质量文档,理解前端工程化的重要性,比如编写类型定义文件、单元测试、Eslint 和 Prettier 等工具的使用。

  • 最有用的学习内容:

  • 组件化思维:将 UI 拆分成独立、可复用的部分,这不仅有助于项目维护,也有利于团队协作。

  • 状态管理策略:理解和掌握如何有效地管理和同步应用状态,使复杂应用的逻辑更加清晰易懂。

  • 框架的核心 API 与设计理念:深入理解所选前端框架的设计原则和运作机制,能够在实际场景中灵活运用各种 API 解决问题。

在前端框架搭建中,学习到的特别有用的知识包括但不限于以下几个方面:

  • 组件化开发:

理解组件化的基本原理和实践,学会将复杂的用户界面分解成可重用、可组合的组件。在 React、Vue 或 Angular 中,组件是前端框架的核心概念,通过组件化可以极大地提升代码的复用性和可维护性。

  • 状态管理:

掌握如何有效管理组件和整个应用的状态,例如在 React 中使用 useState、useReducer、useContext 等 Hook,或是在大型应用中采用 Redux、MobX 或框架内置的状态管理解决方案(如 Vue 的 Vuex)。

  • 路由管理:

学习如何规划和实施单页面应用(SPA)的路由系统,理解路由映射规则、页面切换逻辑以及传参机制,这在 React Router、Vue Router 等路由库中尤为重要。

  • 异步编程与数据请求:

熟悉前端与后端进行数据交互的方式,如使用 fetch、axios 等库发送 HTTP 请求,处理异步数据流和错误处理,这对任何前端框架都是不可或缺的技能。

  • 框架特定 API 与生态系统:

对所选用框架的核心 API 有深入理解,比如 React 的生命周期、Vue 的指令系统、Angular 的数据绑定和依赖注入等。同时,熟悉框架周边生态工具和服务,如构建工具 Webpack/Babel、状态管理库、UI 组件库等。

  • 性能优化:

学会针对前端性能进行优化,包括但不限于代码分割、懒加载、缓存策略、图片优化、减少重排与重绘等手段。

  • 测试与调试:

掌握单元测试、集成测试以及端到端测试等不同类型的测试方法,以及如何使用 DevTools 等工具进行代码调试,这对于保证代码质量至关重要。

  • 工程化与标准化:

了解现代前端项目的工程化实践,包括代码组织结构、版本控制、持续集成/持续部署(CI/CD)、代码规范(ESLint 等)、自动格式化(Prettier)、类型检查(TypeScript)等。

综上所述,前端框架搭建过程中,理解和熟练运用上述知识点能显著提升开发效率和产品质量。特别是组件化开发和状态管理这两方面的能力,对于构建高质量的前端应用具有非常实用的价值。

5. 有在老项目上做过哪些优化?比如重构、升级、性能优化等?

  1. 优化了组件的复用,避免了重复的代码,提高代码的可维护性。

6. React 项目升级过程中遇到的坑,从 17 升级到 18 有哪些坑?

并没有做升级,项目使用的是 React 17。当时创建的时候 React 18 还没有正式发布,所以使用了 React 17。后续项目稳步推进,对于升级也没有特别大的需要。

https://juejin.cn/post/7094037148088664078

以下是 React 18 最重要的三个升级点:

  1. 并发渲染 (Concurrent Rendering)

React 18 引入了并发渲染模式,这是其最突出的新特性。并发渲染使得 React 能够在不影响用户体验的前提下,更高效地处理多个状态更新任务。React 现在可以暂停、优先处理更高优先级的任务,然后再恢复被暂停的任务,实现了渲染过程的分阶段和可中断性。这一特性主要体现在 Suspense、startTransition API 和 useDeferredValue hook 的引入。

Suspense: 允许 React 在等待异步数据加载时挂起渲染,并展示占位符内容,当数据准备完毕后再恢复渲染。

startTransition(): 这个 API 可用于标记非关键状态更新,React 可以根据系统负载选择是否延迟执行这些更新。

useDeferredValue(): 这个 hook 帮助组件处理可能延迟更新的值,尤其适用于性能敏感的情况。

  1. 服务器组件(Server Components)

React 18 引入了实验性的服务器组件功能,旨在优化首次加载性能和减少客户端渲染负担。服务器组件能够在服务器端运行并生成 HTML,只向客户端发送最小化数据,进而提高首屏加载速度。

  1. Streaming React Server Render (SSR)

React 18 改进了服务器端渲染(SSR)的性能,通过增量渲染(Incremental Rendering)和流式传输(Streaming)技术,允许服务器在接收到部分数据后就开始向客户端发送 HTML,而不是等待所有数据准备好再一次性输出。这种增量渲染模式可以显著改善用户体验,尤其是在加载大页面或网络条件较差的情况下。

此外,React 18 还包括对 Strict Mode 的更新以及其他一些稳定性改进,不过并发渲染及其相关 API 是其中最显著的变化。

7. 在项目比较大的情况下,有考虑做 TS 的介入吗?

如果是在项目初期,为了减少重复代码,以及代码的维护性,以及代码的可读性。

8. 购物网站使用 Next.js 的原因是什么?

Umi 是一个以路由为中心的框架,支持快速构建组件、页面和路由。它具有易于扩展的插件体系结构,支持在生产环境中进行代码切割,并且拥有内置的打包和部署工具。

Next.js 是一个服务器端渲染 (SSR) 的框架,提供了方便的页面导航和强大的 SEO 解决方案。Next.js 还提供了一些高级特性,例如支持自动代码切割,并且可以很容易地部署到云平台上,如 Vercel、静态文件服务、CSS 模块化等。

Next.js 作为一款流行的 React 服务端渲染框架,它的优势主要包括:

  • 服务端渲染 (SSR):Next.js 默认支持服务端渲染,这有助于提高 SEO(搜索引擎优化),因为爬虫程序能够抓取到完整的 HTML 页面内容,同时也提升了首次加载页面的速度和用户体验。

  • 静态站点生成 (SSG):Next.js 9.3 版本开始引入静态站点生成功能,可以预先生成纯静态 HTML 文件,适用于不需要服务器动态渲染的场景,大大提高了静态页面的加载速度。

  • 自动代码分割:Next.js 自动处理代码分割,使得应用能够按需加载,减少初始加载体积,提高加载速度。

  • 路由系统:Next.js 内置了基于文件系统的简单直观路由系统,开发者只需按照约定的目录结构存放文件,即可自动生成相应的路由。

  • 静态导出与预渲染:Next.js 提供了静态导出功能,可以方便地将应用部署为完全静态网站,也支持预渲染以提高特定页面的加载速度。

  • API Routes:Next.js 允许在同一个项目中创建 API 路由,简化了前后端一体化的开发流程。

  • 开发体验:Next.js 提供了开箱即用的开发环境,包含热加载、错误回溯等开发者友好的功能,以及对 TypeScript 的原生支持。

对比 Umi.js,两者均属于基于 React 的优秀框架,Umi.js 也有着自己独特的亮点:

  • 强大的路由系统:Umi.js 的路由系统功能强大,支持动态导入、嵌套路由等高级功能,以及按需加载。

  • 丰富的生态集成:Umi.js 开箱即用地集成了 Ant Design UI 库、Dva 数据流管理库、Qiankun 微前端方案等,对于阿里巴巴系生态有着很好的支持。

  • 高度可配置性:Umi.js 提供了更多定制化选项,可以根据项目需求灵活配置不同的构建工具、样式处理器等。

  • 企业级特性:Umi.js 因为出自支付宝团队,所以在企业级应用的开发场景下有许多针对性的功能设计,如多环境变量配置、权限管理、国际化等。

总的来说,Next.js 更偏向于 SSR 与 SSG 的无缝集成,提供了优秀的默认配置和出色的生产环境性能优化,而 Umi.js 则注重于中国开发者社区的生态整合与企业级项目的深度定制需求。选择哪个框架取决于具体的项目需求和团队偏好。

9. SSR 除了搜索引擎可以辅助优化排名之外,还有什么样的优势?比如性能方面、页面方面,其实 Next.js 的成本相对于 umi 的成本要高一点,除了 SEO 的优化来说,应该会有更大的优势。

服务端渲染的话其实相对于已经把页面节点都已经渲染出来了,如果是 umi 那种 的加载方式的话

10. 主要负责哪些模块(后续针对模块来提问)

11. 订单支付方式在接入的时候有没有遇到什么问题?

12. 解释下简历中写的轮询查询订单状态

13. TS 有哪些类型?高阶类型有哪些?

Typescript 高阶类型

TypeScript 高级类型清单(附 demo)

说说你对 TypeScript 中高级类型的理解?有哪些?

14. 了解 React 中的 diff 算法吗?

js
// diff 算法是一种用于比较新旧虚拟 DOM 树的差异的算法,目标是找出需要更新的部分,并生成一个最小化的 DOM 操作序列。

diff 计算发生在更新阶段,当第一次渲染完成后,就会产生 Fiber 树,再次渲染的时候(更新),就会拿新的 JSX 对象(vdom)和旧的 FiberNode 节点进行一个对比,再决定如何来产生新的 FiberNode,它的目标是尽可能的复用已有的 Fiber 节点。这个就是 diff 算法。

在 React 中整个 diff 分为单节点 diff 和多节点 diff。

所谓单节点是指新的节点为单一节点,但是旧节点的数量是不一定的。

单节点 diff 是否能够复用遵循如下的顺序:

  1. 判断 key 是否相同
  • 如果更新前后均未设置 key,则 key 均为 null,也属于相同的情况
  • 如果 key 相同,进入步骤 2
  • 如果 key 不同,则无需判断 type,结果为不能复用(有兄弟节点还会去遍历兄弟节点)
  1. 如果 key 相同,再判断 type 是否相同
  • 如果 type 相同,那么就复用
  • 如果 type 不同,则无法复用(并且兄弟节点也一并标记为删除)

多节点 diff 会分为两轮遍历:

第一轮遍历会从前往后进行遍历,存在以下三种情况:

  • 如果新旧子节点的 key 和 type 都相同,说明可以复用
  • 如果新旧子节点的 key 相同,但是 type 不同,这个时候就会根据 ReactElement 来生成一个全新的 fiber,旧的 fiber 被放入到 deletions 数组里面,回头统一删除。但是注意,此时遍历并不会终止
  • 如果新旧子节点的 key 和 type 都不相同,结束遍历

如果第一轮遍历被提前终止了,那么意味着还有新的 JSX 元素或者旧的 FiberNode 没有被遍历,因此会采用第二轮遍历去处理。

第二轮遍历会遇到三种情况:

  • 只剩下旧子节点:将旧的子节点添加到 deletions 数组里面直接删除(删除的情况)
  • 只剩下新的 JSX 元素:根据 ReactElement 元素来创建 FiberNode 节点(新增的情况)
  • 新旧子节点都有剩余:会将剩余的 FiberNode 节点放入一个 map 里面,遍历剩余的新的 JSX 元素,然后从 map 中去寻找能够复用的 FiberNode 节点,如果能够找到,就拿来复用(移动的情况) 如果不能找到,就新增。然后如果剩余的 JSX 元素都遍历完了,map 结构中还有剩余的 Fiber 节点,就将这些 Fiber 节点添加到 deletions 数组里面,之后统一做删除操作。

整个 diff 算法最核心的就是两个字“复用”。

React 不使用双端 diff 的原因:

由于双端 diff 需要向前查找节点,但每个 FiberNode 节点上都没有反向指针,即前一个 FiberNode 通过 sibling 属性指向后一个 FiberNode,只能从前往后遍历,而不能反过来,因此该算法无法通过双端搜索来进行优化。 React 想看下现在用这种方式能走多远,如果这种方式不理想,以后再考虑实现双端 diff。React 认为对于列表反转和需要进行双端搜索的场景是少见的,所以在这一版的实现中,先不对 bad case 做额外的优化。

15. React 有哪些常用的 hooks?

1. useState

用于在函数组件中添加状态。状态(state)是变化的数据,是组件甚至前端应用的核心。useState 有传入值和函数两种参数,返回的 setState 也有传入值和函数两种参数。

js
// 传入值
const [state, setState] = useState(0);
// 传入函数
const [num, setNum] = useState(() => {
    let a = 1,
        b = 2;
    return a + b;
});

setState(1);
setNum((state) => state + 1); // 函数的参数是上一次的 state

2. useEffect

用于在函数组件中执行副作用操作,例如 数据获取、订阅或手动更改 React 组件的 DOM 等。副作用 effect 函数是在渲染之外额外执行的一些逻辑。它是根据第二个参数的依赖数组是否变化来决定是否执行 effect,可以返回一个清理函数,会在组件卸载前执行或每次使用更改的依赖项重新渲染之前运行。

执行时机:在渲染结束之后

js
useEffect(() => {
    let timer = setTimeout(() => {
        console.log(num);
    }, 5000);
    return () => {
        // 清理函数
        clearTimeout(timer);
    };
}, [num]); // 如果不传第二个参数时,每次都会执行;传递第二个参数时,第二个参数有变化时执行
  1. useLayoutEffect

和 useEffect 差不多,但是 useEffect 的 effect 函数是异步执行的,所以可能中间有次渲染会闪屏。而 useLayoutEffect 是同步执行的,所以不会闪屏,但如果计算量大可能会导致掉帧,阻塞渲染。所以,仅当在浏览器渲染之前运行效果至关重要时才需要此功能,例如:在用户看到工具提示之前测量和定位工具提示。(只有在关键时刻需要在用户看到之前运行你的 Effect 时才需要使用它,例如,在显示提示工具提示之前测量和定位位置。)

  1. useInsertionEffect

回调函数会在 commit 阶段的 Mutation 子阶段同步执行,与 useLayoutEffect 的区别在于执行的时候无法访问 DOM 的引用。这个 Hook 是专门为 CSS-in-JS 库插入全局的 style 元素而设计。

5. useReducer

用于在函数组件中管理复杂的状态逻辑。它接受一个 reducer 函数和一个初始状态值作为参数,并返回一个包含当前状态和一个更新状态的 dispatch 函数的数组。使用 useReducer 可以更好地组织和管理状态更新逻辑,特别是在处理多个状态变量或执行异步操作时。

封装一些修改状态的逻辑到 reducer,通过 action 触发。当修改深层对象的时候,创建新对象比较麻烦,可以结合 immer 来解决。

6. useRef

可以保存 dom 引用或其他内容,通过.current来取,改变它的内容不会触发重新渲染。

返回一个可变的 ref 对象,其.current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。这对于管理 DOM 对象、定时器或其他需要在组件生命周期内保持引用的值很有用。

  1. forwardRef + useImperativeHandle

通过 forwardRef 可以从子组件转发 ref 到父组件。如果想自定义 ref 内容可以使用 useImperativeHandle 来实现。

8. useContext

用于在函数组件中访问 React 的 Context API。它接受一个 Context 对象作为参数,并返回该 Context 的当前值。这样,你可以在函数组件中使用 Context,而无需手动传递 props。

9. memo + useMemo + useCallback

memo 包裹的组件只有在 props 变化的时候才会重新渲染,useMemo、useCallback 可以防止 props 不必要的变化,两者一般是结合使用。不过当用来缓存计算结果等场景的时候,也可以单独使用 useMemo、useCallback。

useMemo 返回一个记忆化的值,该值只在依赖项数组发生变化时才会重新计算。这对于避免重复计算和提高性能很有用。

useCallback 返回一个记忆化的版本的回调函数,该回调函数在依赖项数组发生变化时才会更新。这对于防止不必要的渲染和提高性能很有用。

16. useMemo 和 useCallback 之间的区别?

useMemo 返回一个记忆化的值,该值只在依赖项数组发生变化时才会重新计算。这对于避免重复计算和提高性能很有用。

useCallback 返回一个记忆化的版本的回调函数,该回调函数在依赖项数组发生变化时才会更新。这对于防止不必要的渲染和提高性能很有用。

17. useRef 使用的场景有哪些?说一些高阶的用法。为什么 Ref 上面挂了一个变量就可以拿到 form 表单的一些方法或者是一些属性了?有了解过吗?

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

作用

  1. 获取 dom 节点
  2. 获取组件的实例
  3. 存储一些变量

在 React 中,refs(引用)是用于访问组件或 DOM 元素的方法。

  • 访问组件实例:通过 refs,可以获取到组件的实例,从而可以直接调用组件的方法或访问组件的属性。这在某些情况下非常有用,例如需要手动触发组件的某个方法或获取组件的状态。
  • 访问 DOM 元素:通过 refs,可以获取到 React 组件中的 DOM 元素,从而可以直接操作 DOM,例如改变样式、获取输入框的值等。这在需要直接操作 DOM 的场景下非常有用,但在 React 中应该尽量避免直接操作 DOM,而是通过状态和属性来控制组件的渲染。

18. 了解 React 的 Fiber 吗?为什么 React 16 之后要改成 Fiber?有什么好处?if else 里面不能使用自定义 hook,这是为什么呢?

Fiber 是 React 中一种新的架构,它用于实现增量式的、可中断的虚拟 DOM diff 过程。 Fiber 的目标是改进 React 的性能和用户体验,使得 React 应用程序更加流畅和响应。 在 React 的旧版本中,虚拟 DOM diff 过程是一个递归的过程,它会一直执行直到完成,期间无法中断。 这可能会导致长时间的 JavaScript 执行,从而阻塞主线程,造成页面的卡顿和不流畅的用户体验。 为了解决这个问题, React 引入了 Fiber 架构。 Fiber 将整个虚拟 DOM diff 过程分为多个小任务,每个任务称为一个 Fiber 节点。 这些 Fiber 节点被组织成一个树状结构,称为 Fiber 树。 Fiber 树可以被中断和恢复,这意味着在执行 Fiber 树的 diff 过程时,可以在任意时刻中断当前任务,并优先执行其他任务。 这样可以使得应用程序更加灵活地响应用户的交互和其他优先级的任务,提高性能和响应性。 通过 Fiber 架构, React 可以根据任务的优先级动态地调整任务的执行顺序,从而更好地控制 JavaScript 的执行。 这使得 React 应用程序可以在不阻塞主线程的情况下进行虚拟 DOM diff,减少页面的卡顿和提高用户体验。 总而言之, Fiber 是 React 中一种新的架构,用于实现增量式的、可中断的虚拟 DOM diff 过程。 它通过将 diff 过程分为多个小任务,并根据优先级动态地调整任务的执行顺序,提高 React 应用程序的性能和响应性。