作为一名面试官,我观察到当前前端招聘市场中的现象并非偶然。这背后所折射出的问题,远不止是候选人“临时抱佛脚”那么简单,而更像是一种供需之间的结构性错位。
如今 Web 前端技术更新迭代速度极快,但与此同时,企业对基础能力的考察却愈发深入。当一份简历上堆砌着 Vue、React、Webpack、Vite 等热门框架和工具时,面试官自然会默认求职者至少理解这些技术背后的运行机制。然而现实情况往往是:只知其然,不知其所以然。
这种落差究竟是源于求职者的投机心态,还是企业设定了过高的门槛?接下来,我想从自身经验出发,谈谈我认为前端开发者在准备面试时真正应该关注的核心内容。
JavaScript 相关问题
JavaScript 作为前端开发的基石,始终是面试中的重点考察方向。以下是一些常被问及但容易混淆的知识点:
- 哪些事件不会触发冒泡?
在浏览器环境中,大多数事件遵循 DOM 事件流模型,会从目标元素逐级向上传播(即冒泡)。但也存在例外:focus:元素获取焦点时触发,如通过点击或键盘操作,该事件不冒泡。blur:元素失去焦点时触发,同样不会冒泡。focusin:与 focus 类似,但支持冒泡行为,可在父级监听。focusout:与 blur 对应,具备冒泡特性。load:资源加载完成时触发,例如 img 标签上的 load 事件不会向上冒泡。unload:页面即将卸载时触发,用于清理工作,也不冒泡。stop:通常与 audio 或 video 元素相关,在媒体停止播放时触发。readystatechange:document 的 readyState 发生变化时触发,常见于页面加载过程。scroll:元素滚动时触发,部分浏览器中可能表现不同,某些版本下不冒泡。
- mouseenter 和 mouseover 的区别是什么?
- MessageChannel 的用途及典型使用场景有哪些?
- async/await 的实现原理是怎样的?
- Proxy 是否能够监听对象嵌套属性的引用变化?
- 如何让
var [a, b] = {a: 1, b: 2}解构成功? - 以下代码的输出结果是什么?
- 请描述某段代码的执行流程和最终结果。
- 什么是作用域链?它是如何工作的?
- bind、call、apply 三者有何异同?如何手动实现一个 bind 函数?
- CommonJS 与 ES6 模块系统的引入方式有何差异?
- 简述 Vue3 中响应式系统的设计原理。
- <script> 标签放置在 head 与 body 底部的区别是什么?
- 点击“+3”按钮后,age 的值会变成多少?
- 在 Vue 中,created 与 mounted 两个生命周期钩子之间的时间间隔受哪些因素影响?
- Vue 推荐在哪个生命周期阶段发起网络请求?
React 面试要点
React 以其组件化思想和高效的更新机制广受欢迎,但在实际面试中,很多候选人对其底层逻辑掌握不足。以下是高频问题整理:
- 点击“+3”按钮后,age 的值是多少?
1 import { useState } from 'react'; 2 3 export default function Counter() { 4 const [age, setAge] = useState(42); 5 function increment() { 6 setAge(age + 1); 7 } 8 return ( 9 <> 10 <h1>Your age: {age}</h1> 11 <button onClick={() => { 12 increment(); 13 increment(); 14 increment(); 15 }}>+3</button> 16 </> 17 ); 18 }
实际上,age 可能仅增加 1,最终变为 43。这是因为setAge(age + 1)是异步操作,多次调用会被合并处理,导致只触发一次渲染。
若希望准确累加三次,应改用函数式更新方式:
1 function increment() { 2 setAge(a => a + 1); // 函数式更新 3 } - React Portals 主要用于什么场景?
- react 与 react-dom 之间的关系是什么?
- 为何 React 不直接使用 requestIdleCallback?
- 为什么 React 需要 Fiber 架构,而 Vue 却不需要类似的结构?
- 如果子组件使用了 Portal,点击事件能否冒泡回父组件?
- React 中 render 方法的原理是什么?在哪些情况下会被触发?
- React 合成事件与原生 DOM 事件的执行顺序是怎样的?
- 如何理解受控组件与非受控组件?各自适用的应用场景有哪些?
- 在 React 项目中如何使用 Redux?项目目录结构如何组织?
- 对 Redux 中间件的理解是什么?常用的中间件有哪些?其实现机制是怎样的?
- 谈谈你对 Redux 整体架构的理解及其工作流程。
- 如何理解 immutable 数据?在 React 项目中如何应用?
- JSX 是如何转换为真实 DOM 节点的?
- 在 React 项目中,你是如何进行错误捕获和边界处理的?
Vue 深层解析
尽管 Vue 提供了简洁易用的 API,但其内部机制仍需深入理解,尤其是在性能优化方面:
- 既然 Vue 已经实现了数据响应式,为何还需要 diff 算法?
数据响应式与虚拟 DOM 的 diff 机制解决的是两类不同的问题,二者协同工作以提升整体渲染效率。
数据响应式机制:
Vue 利用Object.defineProperty(Vue2)或Proxy(Vue3)来追踪依赖关系,确保当数据发生变化时,能精确通知到相关的视图部分需要更新。它解决了“何时更新”的问题。
Diff 算法的作用:
虚拟 DOM 的 diff 过程则负责解决“如何更新”的问题。即使知道某个组件需要重新渲染,也不能直接替换整个 DOM 结构,那样代价太高。因此,通过对比新旧 VNode 树的差异,最小化实际 DOM 操作,从而提高性能。
数据绑定确保了视图与数据之间的同步更新。当数据发生变化时,视图会自动进行响应式更新,从而避免了手动操作 DOM 所带来的复杂性和潜在错误。
依赖追踪是 Vue 实现响应式的核心机制之一。它能够精确记录哪些组件或计算属性依赖于特定的数据字段。一旦这些数据发生变动,Vue 能够智能地触发相关依赖的更新流程,保证界面始终与数据状态一致。
虚拟 DOM 与 Diff 算法
虚拟 DOM 是对真实 DOM 的轻量级抽象,存在于内存中。它通过 JavaScript 对象模拟 DOM 结构,使得框架可以在不直接操作浏览器 DOM 的情况下进行 UI 更新。
Diff 算法则是用于比对新旧虚拟 DOM 树之间差异的高效策略。通过对节点进行最小化对比,仅将实际变化的部分应用到真实 DOM 上,显著提升了页面渲染性能。
为何需要 Diff 算法?
1. 性能优化: 直接操作真实 DOM 开销巨大。利用虚拟 DOM 可在内存中快速完成差异计算,结合 Diff 算法可最大限度减少 DOM 操作次数和范围,从而提升整体渲染效率。
2. 批量更新: 多次数据变更可能引发多个 DOM 修改请求。Diff 算法支持将这些操作合并为一次批量更新,有效降低浏览器重排(reflow)和重绘(repaint)的频率。
3. 跨平台兼容: 借助虚拟 DOM 和统一的 Diff 逻辑,Vue 可运行于多种环境,如 Web 浏览器、Weex 等原生容器中,实现一致的渲染行为与响应式机制。
4. 更新效率: 尽管响应式系统能自动感知数据变化,但若每次均直接操作真实 DOM,仍可能导致性能瓶颈。Diff 算法通过精准识别变更区域,只更新必要节点,进一步优化了更新过程。
综合协作机制
Vue 的数据响应式系统与虚拟 DOM + Diff 算法协同工作,各司其职:
- 数据响应式: 实现数据与视图的自动同步,极大简化开发流程,提供直观的编程体验。
- 虚拟 DOM + Diff 算法: 提升渲染性能,减少不必要的 DOM 操作,保障应用的流畅性与高响应速度。
两者结合,分别解决“何时更新”和“如何高效更新”的问题,共同构建出高性能、易维护的前端框架体系。
关于 script 标签位置的性能影响
将 <script> 标签放置在 <head> 或 <body> 底部,会对页面加载行为产生不同影响。
<script> 放在 <head> 中
优点:
- 预加载优势: 浏览器会在解析 HTML 内容前优先下载并执行头部脚本,有助于提前准备所需资源。
- 全局可用性: 适用于需在页面初始阶段即生效的脚本,例如全局配置、监控代码等。
缺点:
- 阻塞渲染: 遇到 <script> 标签时,浏览器会暂停 HTML 解析,直到脚本加载并执行完毕,导致白屏时间延长。
- 首屏延迟: 用户可能长时间面对空白页面,影响体验,尤其在网络较慢时更为明显。
<script> 放在 <body> 底部
优点:
- 非阻塞性: 页面内容可优先被解析和展示,用户能更快看到可视元素,提升感知性能。
- 更好用户体验: 避免因脚本加载造成长时间空白,增强交互即时感。
缺点:
- 执行延迟: 若某些脚本需在 DOM 构建前运行(如初始化逻辑),置于底部可能导致功能异常或延迟触发。
现代优化手段:defer 属性
使用 defer 属性可在 <head> 中安全引入脚本。该属性指示浏览器异步下载脚本文件,但延迟至整个 HTML 文档解析完成后才执行,既保持了脚本的全局可访问性,又不会阻塞页面渲染流程。
1 <script src="script.js" defer></script>2. async 属性:async 属性用于实现脚本的异步加载。当浏览器遇到带有 async 标记的 script 标签时,会立即开始下载该脚本,而不会阻塞页面的解析过程。一旦脚本下载完成,就会立刻执行,无论此时页面是否已经解析完毕。因此,它适用于那些独立性强、不依赖 DOM 结构或其他脚本逻辑的外部脚本。
1 <script src="script.js" async></script>
总结
- <head> 中引入脚本:适合需要尽早执行的代码,但可能会阻塞页面渲染。
- 放置在 <body> 底部:推荐方式,避免阻塞,提升页面加载速度和用户体验。
- 使用 defer 或 async 属性:现代浏览器广泛支持,可有效平衡脚本执行时机与页面性能之间的关系。
前端性能优化相关问题
- 常见的前端性能指标有哪些?如何进行检测?
- 单页应用(SPA)首屏加载缓慢应如何优化?
- 如何通过 CSS 提升页面渲染性能?
- 站点内的图片资源应如何进行性能优化?
- 虚拟 DOM 是否一定比原生操作更快?
- 部分框架并未采用虚拟 DOM,但仍具备良好性能的原因是什么?
- 若某个页面需执行数百个函数,该如何优化其运行效率?
- 简述 png8、png16 与 png32 的区别,并说明 PNG 的压缩机制原理。
- React.memo() 与 useMemo() 的使用场景及主要差异是什么?
- 导致页面长时间白屏的因素有哪些?相应的优化策略是什么?
- 面对包含十万条数据的列表,应如何高效展示?
- DNS 预解析的作用是什么?具体如何实现?
- 在 React 项目中可以采取哪些性能优化手段?
- 浏览器为何要限制请求的并发数量?
前端工程化
1. package.json 中 devDependencies 与 dependencies 的区别
在前端项目的 package.json 文件中,dependencies 和 devDependencies 均用于声明项目所依赖的第三方包,但它们的应用场景和部署行为存在明显差异。
dependencies
- 存放项目在生产环境中正常运行所必需的依赖包。
- 包括核心库、框架、运行时工具等。
- 执行 npm install 或 npm ci 时,默认安装这些依赖。
- 这些包会被打包进最终的构建产物并部署上线,是项目运行不可或缺的部分。
devDependencies
- 用于记录开发阶段所需的辅助性依赖项。
- 常见类型包括测试框架、构建工具、代码格式化器、静态检查工具等。
- 默认情况下,npm install 不会自动安装 devDependencies 中的包;如需安装,需显式添加 --dev 或 --only=dev 参数。
- 这些依赖不会被包含在生产构建中,仅服务于本地开发、测试和构建流程。
总体而言,dependencies 关注的是“运行时必需”,而 devDependencies 聚焦于“开发期辅助”。
- webpack 5 相较于早期版本的主要升级点有哪些?
- vite 的底层实现原理是什么?
- 除 webpack 外还有哪些类似的构建工具?它们之间有何异同?
- 如何利用 webpack 实现前端性能的优化?
- webpack proxy 的工作机制是什么?为何能解决跨域问题?
- webpack 的热更新功能是如何实现的?其核心原理是什么?
- Loader 与 Plugin 的本质区别是什么?编写二者的基本思路是怎样的?
- 列举几个常用的 webpack Plugin,并说明其所解决的问题。
- 列举几个常见的 webpack Loader,并解释其用途。
- webpack 的完整构建流程包括哪些步骤?
- 你对 webpack 的整体理解是什么?它解决了哪些关键问题?
- webpack 中 Loader 和 Plugin 的实现机制是怎样的?
- 如何有效提升 webpack 的构建速度?
- webpack-dev-server 的内部工作原理是什么?
- 你了解 babel 吗?能否说明不同 stage 阶段的含义?
最后的灵魂一问
你的目标是进入大厂吗?你应该清楚,这条路上往往伴随着挑战与付出。
你是万里挑一的天才吗?如果不是,那么经历几次失败实属正常。大多数人都是普通人,有时候只是当前阶段不够匹配而已。


雷达卡


京公网安备 11010802022788号







