> 技术文档 > 前端模拟面试

前端模拟面试

作者: github star 和 星星加星 和github 刷星 3人共同主笔

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

面试官:你好,先简单介绍一下自己和最近做的一个项目吧。

在开场介绍环节,建议运用 STAR 法则,清晰阐述项目背景、个人任务、采取行动及最终成果,突出与岗位匹配的技术能力与解决问题的能力。

  • 回答示例:您好!我叫 [姓名],从事前端开发工作 [X] 年了。在技术栈方面,我对 React、Vue 都比较熟悉,也掌握 Webpack 等工程化工具。最近我参与了公司的电商平台优化项目,负责商品详情页和购物车模块的重构工作。在商品详情页,原页面加载速度慢,图片显示不流畅。我通过优化图片加载策略,采用 WebP 格式图片结合懒加载技术,将图片加载时间缩短了 30%。同时,利用 React.memo 和 useMemo 对组件进行性能优化,减少不必要的重渲染。在购物车模块,我实现了实时计算总价、优惠策略应用等功能,通过 Redux 管理购物车状态,确保数据在不同页面间的一致性,提升了用户购物体验,该项目上线后,相关页面的转化率提升了 15% 。

面试官:好的,了解了。那我们先聊聊基础吧。你能讲讲 JavaScript 中nullundefined的区别吗?实际开发中一般在什么场景下使用它们?

这个问题考察对 JavaScript 基础数据类型的理解,回答时需明确两者定义、区别,结合实际场景说明用途。

  • 回答示例nullundefined在 JavaScript 中都表示 “无值”,但本质上有区别。null是一个表示 “空值” 的对象,它是有意设置为无值的,意味着此处原本应该有一个对象,但目前为空。例如,当我们初始化一个变量,准备稍后再给它赋值一个对象时,可能会先将其设为nulllet myObject = null; 。undefined表示变量声明了但未初始化,或者访问对象不存在的属性,函数没有返回值时也会返回undefined。比如let a; console.log(a); ,这里a的值就是undefined 。在实际开发中,null常用于释放对象引用,比如在清除定时器时,将定时器变量设为null,防止内存泄漏:

javascript

let timer = setInterval(() => { // 执行某些操作}, 1000);// 清除定时器clearInterval(timer);timer = null; 

undefined常用于判断函数参数是否传递。例如:

javascript

function greet(name) { if (typeof name === \'undefined\') { name = \'Guest\'; } console.log(`Hello, ${name}!`);}greet(); // 输出Hello, Guest!greet(\'Alice\'); // 输出Hello, Alice! 

面试官:嗯,不错。那再问一个关于异步的问题:setTimeoutPromise.thenasync/await的执行顺序是怎样的?能举个例子说明吗?

这道题聚焦 JavaScript 异步机制,需掌握事件循环中宏任务、微任务概念,通过示例清晰阐述执行顺序。

  • 回答示例:在 JavaScript 的事件循环机制中,setTimeout属于宏任务,Promise.then属于微任务,async/await本质上也是基于 Promise 实现的。执行顺序是先执行同步代码,然后处理微任务队列,最后处理宏任务队列。例如:

javascript

console.log(\'start\');setTimeout(() => { console.log(\'setTimeout\');}, 0);Promise.resolve().then(() => { console.log(\'Promise.then\');});async function asyncFunc() { console.log(\'asyncFunc start\'); await Promise.resolve(); console.log(\'asyncFunc after await\');}asyncFunc();console.log(\'end\');

上述代码执行过程如下:

  1. 首先输出start,这是同步代码。
  2. 遇到setTimeout,将其回调函数放入宏任务队列。
  3. 遇到Promise.resolve().then,将其回调函数放入微任务队列。
  4. 执行asyncFunc,输出asyncFunc start,遇到await Promise.resolve()await后面的代码暂停执行,Promise.resolve()返回一个已解决的 Promise,其.then回调函数放入微任务队列。
  5. 继续执行同步代码,输出end。此时同步代码执行完毕。
  6. 开始处理微任务队列,先输出Promise.then,然后输出asyncFunc after await 。
  7. 最后处理宏任务队列,输出setTimeout 。

面试官:了解了。那聊聊框架吧,你用 React 比较多是吧?能讲讲 React 的虚拟 DOM 和 Diff 算法的核心原理吗?为什么虚拟 DOM 能提升性能?

此问题考察对 React 核心原理的理解,回答需阐述虚拟 DOM 和 Diff 算法概念、工作原理,分析提升性能的原因。

  • 回答示例:虚拟 DOM(Virtual DOM)是 React 为了高效更新 DOM 而引入的概念。它本质上是 JavaScript 对象,用来描述真实 DOM 树的结构。当组件状态或属性发生变化时,React 会创建一棵新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行对比,这个对比过程就是 Diff 算法。Diff 算法的核心原理是:
  1. 只对同级节点进行比较。当 React 发现两棵树的根节点类型不同时,会直接销毁旧树,创建新树。例如,如果原来根节点是
    ,现在变为

    ,React 会直接重新构建整个 DOM 结构。

  2. 通过给元素添加唯一的key属性来标识节点。在对比列表时,key能帮助 React 准确判断哪些节点需要更新、删除或插入。比如有一个列表[
  3. Item1
  4. ,

  5. Item2
  6. ],如果数据变化后变为[

  7. Item2
  8. ,

  9. Item3
  10. ],React 通过key能快速定位到Item1需要删除,Item3需要插入,Item2位置变化但内容未变,只需移动 DOM 节点。
    虚拟 DOM 能提升性能主要原因有:

  11. 减少真实 DOM 操作。传统方式中,只要数据变化就直接操作真实 DOM,而真实 DOM 操作开销很大。虚拟 DOM 通过对比,只有在差异确定后才一次性更新真实 DOM。例如,当一个复杂表单中有多个输入框值变化,如果直接操作真实 DOM,每次值变化都可能触发重排和重绘。而虚拟 DOM 会先计算出所有变化,最后统一更新,大大减少了重排和重绘次数。
  12. 跨平台能力。由于虚拟 DOM 是 JavaScript 对象,不依赖特定平台的 DOM API,使得 React 可以方便地在不同环境(如浏览器、Node.js、移动端 RN 等)中使用,提高了代码复用性 。

面试官:好的。那在 React 中,useState的更新是同步还是异步的?为什么?如果想在状态更新后立即获取最新值,有哪些办法?

该问题深入考察对 React Hook 中useState机制的理解,要明确更新特性及原因,给出获取最新值的方法。

  • 回答示例:在 React 中,useState的更新在合成事件和生命周期函数中是异步的,在原生 DOM 事件和setTimeout等宏任务中是同步的。这是因为 React 为了提高性能,在合成事件和生命周期函数中批量处理状态更新。例如:

javascript

import React, { useState } from\'react\';function App() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); console.log(count); // 输出0,因为是异步更新 }; return ( 

{count}

);}

handleClick函数中,setCount(count + 1)执行后,立即打印count还是旧值 0 。
之所以这样设计,是为了避免频繁触发重新渲染。如果每次useState更新都立即重新渲染,会导致性能问题。比如在一个列表渲染中,有多个元素同时触发状态更新,如果每个更新都立即重新渲染,会造成大量不必要的计算和渲染开销。
如果想在状态更新后立即获取最新值,有以下几种办法:

  1. 使用useEffect 。useEffect会在组件渲染和状态更新后执行,通过依赖项数组可以控制其触发时机。例如:

javascript

import React, { useState, useEffect } from\'react\';function App() { const [count, setCount] = useState(0); useEffect(() => { console.log(count); // 每次count更新后会打印最新值 }, [count]); const handleClick = () => { setCount(count + 1); }; return ( 

{count}

);}
  1. setState的回调函数中获取(在类组件中常用,React Hook 中不推荐但也可用)。例如在类组件中:

javascript

import React, { Component } from\'react\';class App extends Component { constructor(props) { super(props); this.state = { count: 0 }; } handleClick = () => { this.setState((prevState) => ({ count: prevState.count + 1 }), () => { console.log(this.state.count); // 输出最新值 }); }; render() { return ( 

{this.state.count}

); }}
  1. 使用useReducer替代useState 。useReducer可以更精细地控制状态更新逻辑,在某些场景下能方便获取最新状态 。

面试官:嗯。再聊聊工程化,你项目中用过 Webpack 或者 Vite 吧?能说说 Vite 为什么比 Webpack 在开发环境启动更快吗?它们的核心区别是什么?

这道题考查对前端构建工具的理解,要分析 Vite 启动快的原因,对比两者核心差异。

  • 回答示例:在项目中我对 Webpack 和 Vite 都有实际使用经验。Vite 在开发环境启动比 Webpack 更快,主要原因如下:
  1. 基于 ES 模块(ESM)。Vite 利用浏览器原生支持的 ESM,直接将源代码提供给浏览器,无需像 Webpack 那样进行复杂的打包过程。例如,在 Webpack 中,它需要将各种类型的模块(如 CommonJS、ES 模块等)统一转换、打包成浏览器可识别的格式,这个过程涉及大量的解析、编译和代码合并。而 Vite 直接利用浏览器对 ESM 的支持,浏览器可以并行加载模块,大大提高了加载速度。
  2. 轻量级依赖预构建。Vite 会在启动时对一些较大的依赖包进行预构建,将其转换为 ESM 格式,并利用缓存机制。在后续开发中,如果依赖没有变化,Vite 可以快速复用缓存,减少重复构建工作。相比之下,Webpack 每次启动都需要重新分析和构建整个项目依赖,即使依赖没有变化,也可能因为其构建机制的特性,导致较长的启动时间。
    Webpack 和 Vite 的核心区别主要体现在:
  3. 构建理念。Webpack 是基于打包的构建工具,它会将项目中的各种资源(JavaScript、CSS、图片等)都打包成一个或多个 bundle 文件,适合大型复杂项目,通过各种 Loader 和 Plugin 可以实现丰富的功能扩展,如代码压缩、图片优化、热更新等。Vite 则是基于原生 ESM 的开发服务器和构建工具,开发阶段以快速的冷启动和热更新为优势,生产阶段才进行打包优化,更适合现代前端开发注重快速迭代的特点,尤其是在开发小型项目或对启动速度要求极高的场景下表现出色。
  4. 配置复杂度。Webpack 的配置相对复杂,需要开发者详细配置各种 Loader(如处理 CSS 的css - loaderstyle - loader,处理图片的file - loader等)和 Plugin(如html - webpack - plugin用于生成 HTML 文件,mini - css - extract - plugin用于提取 CSS 到单独文件等)来满足不同的项目需求。而 Vite 的配置相对简洁,它有一套合理的默认配置,对于大多数常见场景,开发者无需过多配置即可快速上手,只有在项目有特殊需求时才需要进行额外配置 。

面试官:了解了。那性能优化方面,你在项目中做过哪些具体的优化措施?比如首屏加载慢,你是怎么解决的?

本题考查实际项目中的性能优化能力,需结合项目经历,列举优化措施及解决首屏加载慢的方法。

  • 回答示例:在项目中,针对性能优化我采取了多方面措施。
  1. 资源优化:
    • 图片优化。将图片转换为 WebP 格式,这种格式在保证图片质量的同时,文件大小比 JPEG、PNG 等传统格式更小。例如,项目中的产品展示图片,转换为 WebP 格式后,平均文件大小减少了 30% - 40% 。同时,采用图片懒加载技术,只有当图片进入视口时才进行加载。在 React 项目中,使用react - lazyload库实现图片懒加载,避免页面初始加载时大量图片同时请求,减少带宽占用。
    • 代码压缩与合并。使用 Webpack 的terser - webpack - plugin对 JavaScript 代码进行压缩,去除冗余代码和注释,减小文件体积。同时,将多个 CSS 文件合并为一个,减少 HTTP 请求次数。例如,原本项目中有 5 个 CSS 文件,合并后 HTTP 请求减少了 4 次。
  2. 网络优化:
    • 启用 CDN。将项目中的静态资源(如图片、CSS、JavaScript 文件)部署到 CDN 节点上,利用 CDN 的全球分布式节点,根据用户地理位置就近提供资源,加快资源加载速度。例如,将项目部署到阿里云 CDN 后,海外用户访问速度提升明显。
    • 优化 HTTP 缓存策略。设置合理的缓存过期时间,对于不常更新的静态资源(如字体文件、一些基础 CSS 和 JavaScript 库),设置较长的缓存过期时间(如一年),减少重复请求。通过在服务器端配置Cache - ControlExpires头信息实现。
  3. 针对首屏加载慢的问题,除了上述通用优化措施外,还采取了以下方法:
    • 服务器端渲染(SSR)。在 React 项目中,使用 Next.js 框架实现 SSR。通过 SSR,服务器将渲染好的 HTML 页面直接返回给浏览器,浏览器无需等待 JavaScript 下载和执行后再渲染页面,大大加快了首屏显示速度。例如,采用 SSR 后,首屏加载时间从原来的 3 秒缩短到 1.5 秒。
    • 骨架屏。在页面数据未加载完成前,展示一个骨架屏占位。在 Vue 项目中,使用vue - skeleton - webpack - plugin生成骨架屏,让用户在等待数据加载过程中有更好的体验,避免页面长时间空白 。

面试官:最后来道编程题吧。请手写一个防抖函数(debounce),并说明它的应用场景。

这是一道编程实践题,需要现场手写代码实现防抖函数,并阐述其应用场景。

  • 回答示例:防抖函数的作用是在一定时间内多次触发同一事件,只执行最后一次。实现如下:

javascript

function debounce(func, delay) { let timer; return function() { const context = this; const args = arguments; clearTimeout(timer); timer = setTimeout(() => { func.apply(context, args); }, delay); };}

解释一下代码:首先定义一个debounce函数,它接收两个参数,一个是需要防抖的函数func,另一个是延迟时间delay 。在debounce函数内部,定义一个timer变量用于存储定时器。然后返回一个新的函数,在这个新函数中,首先获取当前的上下文this和参数arguments,接着清除之前设置的定时器(如果有),防止之前的定时器还未触发就又触发了新的事件。最后重新设置一个定时器,在delay时间后执行原始函数func,并将上下文和参数传递给它。
防抖函数的应用场景很广泛,比如:

  1. 搜索框输入联想。用户在搜索框中不断输入内容时,如果每次输入都触发搜索请求,会造成大量不必要的网络请求。使用防抖函数,当用户停止输入一段时间(如 300 毫秒)后,再触发搜索请求,既能满足用户实时搜索的需求,又能减少网络压力。
  2. 按钮点击提交。防止用户在短时间内多次点击按钮,造成重复提交表单。例如在登录按钮上使用防抖函数,设置延迟时间为 1 秒,在这 1 秒内多次点击按钮,只会执行一次登录请求 。

英文建站知识教程