前端面经(二)
(1)Vue2和Vue3的主要区别?
响应式系统重构(核心变化)
- Vue 2: 使用
Object.defineProperty来劫持数据的 getter 和 setter。它对对象的属性进行监听,但无法直接监听数组索引和长度的变化(需要通过重写数组方法实现),也无法监听对象属性的添加或删除(需要使用Vue.set或Vue.delete)。 - Vue 3: 使用
Proxy重写了响应式系统。
-
- 性能更好: 可以拦截对象的各种操作,包括属性的添加、删除等,无需特殊 API。
- 支持更好的数据结构: 原生支持
Map、Set、WeakMap、WeakSet。 - 惰性递归: 只有在真正访问到的嵌套属性才会被代理,提升了初始化性能。
组合式 API (Composition API)
这是对开发者影响最大、最直观的变化。
- Vue 2 (Options API):
将代码按照选项(data,methods,computed,watch,生命周期等)进行组织。当组件逻辑复杂时,同一个功能的代码会被拆分到不同选项中,导致阅读和维护困难。 - Vue 3 (Composition API):
引入了setup函数,允许你根据逻辑功能来组织代码,而不是根据选项类型。所有相关的逻辑(状态、方法、计算属性、生命周期等)都可以放在一起,使得代码更内聚、更易于复用(通过自定义组合式函数)。
生命周期
- 大部分生命周期钩子都被重新命名,以对应组合式 API 中的函数。
beforeDestroy和destroyed被更名为beforeUnmount和unmounted,更符合其语义。- Vue 3 组合式 API 中,生命周期钩子都以
onXxx的形式存在,如onMounted,onUpdated等。
(2)computed和watch的区别,分别主要运用场所?
|
特性 |
Computed (计算属性) |
Watch (侦听器) |
|
定义 |
基于它们的响应式依赖进行缓存的派生值。 |
监听一个或多个响应式数据源,并在其变化时执行副作用。 |
|
返回值 |
必须返回一个值。 |
没有返回值,通常执行异步或开销较大的操作。 |
|
缓存 |
✅ 有缓存。依赖不变时,直接返回缓存结果。 |
❌ 无缓存。每次变化都会执行函数。 |
|
异步操作 |
❌ 不允许在计算函数内进行异步操作。 |
✅ 允许并常用于异步操作(如API请求)。 |
|
依赖追踪 |
自动追踪所有在内部使用的响应式依赖。 |
显式指定要侦听的一个或多个数据源。 |
|
初始执行 |
默认在首次访问时就会计算。 |
默认情况下,组件初始化时不会执行。可通过 强制立即执行。 |
|
语法 |
作为属性使用(在模板中直接当做数据属性)。 |
是一个函数或对象配置,需要在 |
- 当你想要基于现有数据计算出一个新的数据,并且这个新数据要在模板中展示时,使用
computed。 它高效、简洁,且是声明式的。 - 当需要在数据变化时执行异步请求、复杂的业务逻辑或“副作用”操作时,使用
watch。 它更强大、更灵活,但性能开销相对较大。
一个简单的判断法则:
问自己一个问题:“我是在计算一个值,还是在响应一个变化?”
(3)session和localstorge、cookie的异同?
核心异同概览表
|
特性 |
Cookie |
LocalStorage |
SessionStorage |
|
生命周期 |
可设置过期时间,未设置则随浏览器关闭失效 |
永久存储,除非手动清除 |
会话级,页面关闭即清除 |
|
数据大小 |
~4KB |
~5MB 或更多 |
~5MB 或更多 |
|
与服务端通信 |
会自动在每次请求的 HTTP Header 中携带 |
不参与 |
不参与 |
|
可访问性 |
同源窗口均可访问 |
同源窗口均可访问 |
仅同一浏览器标签页可访问 |
|
API 易用性 |
原生 API 不友好,通常使用库 |
简单的键值对 API |
简单的键值对 API |
|
存储位置 |
浏览器和服务器 |
仅浏览器 |
仅浏览器 |
1. Cookie
Cookie 是为服务端识别用户状态而设计的,因此它的设计核心是与服务端通信。
特点:
- 容量小: 每个域名下的 Cookie 大小限制约为 4KB,数量也有限制(约 20-50 个,因浏览器而异)。
- 自动携带: 浏览器会自动在同源的 HTTP 请求头(
Cookie头)中携带 Cookie 信息,这会影响请求性能(尤其是未使用 HTTP/2 时)。 - 生命周期可控: 通过
Expires或Max-Age设置过期时间。没有设置则成为 Session Cookie,浏览器关闭即失效。 - 安全性: 可以设置
HttpOnly属性来防止 JavaScript 读取,增强安全性(常用于存储身份认证令牌)。也可以设置Secure属性要求仅在 HTTPS 下传输。
主要运用场所:
- 用户身份认证 (Authentication): 存储 Session ID 或 Token,这是其最核心的用途。
- 个性化设置: 如用户的语言偏好、主题设置。
- 跟踪用户行为: 与分析工具(如 Google Analytics)结合使用。
2. LocalStorage
LocalStorage 是 Web Storage API 的一部分,用于纯粹的客户端存储。
特点:
- 容量大: 通常为 5MB 或更多,远大于 Cookie。
- 永久存储: 数据永久保存在浏览器中,除非用户手动清除或通过 JavaScript 删除。
- 不参与服务端通信: 数据不会自动发送到服务器,减少了不必要的数据传输。
- 同源共享: 同一协议、域名、端口下的所有页面和标签页都可以访问相同的数据。
主要运用场所:
- 缓存静态资源: 缓存不常变化的 API 数据、HTML 模板等,提升应用性能。
- 保存用户偏好: 如编辑器的内容草稿、网站的 UI 配置(如侧边栏是否收起)。
- 离线数据存储: 在 PWA 或离线应用中存储关键数据。
3. SessionStorage
SessionStorage 与 LocalStorage 接口完全相同,但生命周期不同。
特点:
- 会话级生命周期: 数据仅在当前浏览器标签页有效。关闭标签页,数据即被清除。
- 标签页隔离: 即使是同一个页面,在不同标签页中打开,它们的 SessionStorage 也是完全隔离的。
- 刷新页面数据保留: 刷新当前标签页,SessionStorage 数据不会丢失。
主要运用场所:
- 单次会话的敏感信息: 存储一次性令牌或表单数据,关闭页面后自动清除,更安全。
- 页面间临时传递数据: 在单页应用(SPA)或同源的多页面间,在同一标签页内跳转时传递复杂参数。
- 表单内容暂存: 防止用户意外刷新页面导致表单内容丢失。
(4)ES6有哪些常用的语法?
- 变量声明:let和const
- 箭头函数
- 模板字符串,通过``来创建字符串,支持嵌入变量与多行文本
- 解构赋值:从数组或对象中提取值,并赋值给变量
- 默认参数:运行为函数参数提供默认值
- 扩展运算符(...),将可迭代对象(如数组、对象)展开
- 剩余参数(...):将不定数量的参数表示为一个数组
- Promise:用于处理异步操作,解决“回调地域”的问题
- 支持模块化:通过export和import将代码拆分到不同的文件中
for...of循环: 遍历可迭代对象(如数组、字符串)的值。
(5)forEach和map的区别与使用场景?
|
特性 |
|
|
|
返回值 |
|
一个新的数组 |
|
目的 |
遍历并执行副作用 |
遍历并转换数据 |
|
是否改变原数组 |
不会(但回调函数可以改变) |
绝对不会 |
|
链式调用 |
不支持(因为返回 ) |
支持(因为返回数组) |
|
性能 |
轻微优势(仅遍历,不创建新数组) |
轻微开销(需要创建和返回新数组) |
|
使用场景 |
执行操作,如打印、DOM 操作、修改外部变量 |
数据转换,如格式化、提取属性、计算新值 |
1. forEach
forEach 方法用于遍历数组的每个元素并对每个元素执行一个函数。这个函数的目的通常是产生"副作用",比如修改外部变量、操作 DOM、调用 API 等。
语法:
array.forEach(function(currentValue, index, arr) {
// 执行某些操作
}, thisValue);
特点:
- 返回值:
undefined - 不改变原数组: 它本身不会改变原数组,但你在回调函数里可以对原数组进行修改。
- 无法中途停止: 不像
for循环可以使用break,forEach会遍历所有元素。 - 使用场景:执行副作用操作(最常用);修改外部变量;操作 DOM 元素。
2. map
map 方法用于遍历数组的每个元素,并对每个元素执行一个函数,然后收集所有函数的返回值,组成一个新的数组。它的核心思想是"映射"或"转换"。
语法:
const newArray = array.map(function(currentValue, index, arr) {
// 返回一个新的值
}, thisValue);
特点:
- 返回值: 一个新的数组,长度与原数组相同。
- 纯函数理念: 理想情况下,
map的回调函数应该是纯函数,即不修改原数组或任何外部状态,只依赖于输入参数返回一个新值。 - 链式调用: 因为它返回一个数组,所以可以继续调用其他数组方法。
- 使用场景:数据转换/格式化(最常用);渲染列表;数学运算。
一个简单的决策法则:
问自己:"我需要从这次遍历中得到一个新数组吗?"
- 需要 -> 使用
map
-
- 例如:将对象数组转换为字符串数组、为每个元素计算新值。
- 不需要,我只是想对每个元素做点事情 -> 使用
forEach
-
- 例如:打印每个元素、将元素保存到数据库、为 DOM 元素添加事件
(6)Vue2与Vue3的生命周期?
下面这个表格汇总了 Vue 3 生命周期函数的主要信息,你可以快速了解其与 Vue 2 的对比和基本用途:
|
生命周期阶段 |
Vue 2 (Options API) |
Vue 3 (Composition API) |
说明与用途 |
|
创建阶段 |
|
❌ 被 替代 |
组件实例开始创建, 、 未初始化 |
|
|
❌ 被 替代 |
组件实例创建完成,可访问 、 ,模板未挂载 |
|
|
挂载阶段 |
|
|
模板编译完成,尚未挂载到 DOM |
|
|
|
模板已挂载到 DOM,可操作 DOM、发起 API 请求 |
|
|
更新阶段 |
|
|
数据变化,虚拟 DOM 重新渲染前 |
|
|
|
数据变化,虚拟 DOM 重新渲染后 |
|
|
卸载阶段 |
|
|
实例销毁前,清理定时器、事件监听 |
|
|
|
实例销毁后,所有绑定已解除 |
|
|
KeepAlive 组件 |
– |
|
被缓存的组件激活时 |
|
– |
|
被缓存的组件停用时 |
(7)onUnmounted一般用于什么场景,处理什么事情?
onUnmounted 在组件实例被完全卸载(销毁)之后调用。此时,组件所有的指令都已解绑、事件监听器被移除、子组件实例也都被销毁。
它的主要用途是执行清理工作,防止内存泄漏和意外的行为。
主要运用场景与处理事项
onUnmounted 就像一个“保洁员”,在组件“离开”(销毁)时,负责打扫干净它留下的“痕迹”。
1. 清除定时器 (最常用)
这是 onUnmounted 最经典的应用场景。如果你在组件中设置了 setInterval 或 setTimeout,必须在组件销毁时清除它们,否则即使组件不存在了,定时器仍然会在后台执行,导致内存泄漏和不可预知的错误。
2. 移除事件监听器
如果你在组件中添加了全局或第三方库的事件监听器(如 addEventListener),也需要在组件销毁时移除它们。
3. 取消异步操作(如 API 请求)
对于已经发送但尚未完成的异步请求(如 Axios 请求),可以使用 CancelToken(Axios)或 AbortController(Fetch API)来取消它们,避免在组件已销毁后还去更新已经不存在的组件状态。
4. 清理第三方库的实例
如果你在组件中初始化了第三方库(如图表库、地图库、富文本编辑器等),通常需要在组件销毁时手动清理它们。
5. 手动清理响应式订阅
如果你手动创建了一些响应式订阅(例如使用 watch 或 watchEffect),它们通常会自动停止。但在某些边缘情况下,你可能需要手动停止。
(8)介绍一下Promise.all?
Promise.all 是一个非常重要的 Promise 并发处理工具,它用于管理多个异步操作,并在所有操作都成功完成后统一处理结果。
Promise.all(iterable) 方法接收一个可迭代对象(通常是一个 Promise 数组),并返回一个新的 Promise。
它的行为特点是:
- 全部成功: 当所有输入的 Promise 都成功完成(fulfilled)时,返回的 Promise 才会成功,结果是一个包含所有 Promise 结果的数组,顺序与输入顺序一致。
- 一个失败即失败: 如果其中任何一个 Promise 被拒绝(rejected),返回的 Promise 会立即拒绝,并返回第一个拒绝的 Promise 的原因。
(9)暂时性死区什么意思?
"暂时性死区"(Temporal Dead Zone,简称 TDZ)是 ES6 中与 let 和 const 变量声明相关的一个重要概念。
暂时性死区 指的是:在代码块内,从块开始到变量声明语句执行前的这段区域,在这段区域中访问该变量会抛出 ReferenceError。
换句话说,变量在声明之前是"不可用"的,不像 var 那样会返回 undefined。
(10)是如何学习前端的?
它考察的是你的学习能力、主动性和成长潜力。公司希望招聘的是一个能持续进化、跟上前端日新月异发展的“学习型”人才。
我的回答会围绕一个核心策略展开:“建立体系、多管齐下、实践驱动、融入社区”。
学习方法分为以下四个层次,由基础到进阶:
层次一:打好基础,建立知识体系(系统性学习)
这是构建我前端知识树的“主干”。
- 官方文档:我的第一选择
-
- 途径: MDN Web Docs、Vue.js 官方文档、React 官方文档、Node.js 官方文档。
- 为什么: 官方文档是最准确、最及时、最权威的信息来源。任何新技术,我的第一步永远是通读其官方文档的核心概念。它能帮我建立最正确的认知,避免被二手资料误导。
- 经典书籍与系统课程:构建深度
-
- 书籍: 《JavaScript高级程序设计》(红宝书)、《你不知道的JavaScript》系列。书籍能提供系统、深入的知识体系,帮助我理解“为什么”。
- 课程: 国内外优秀的大学公开课(如CS61A)或体系化的在线课程(如freeCodeCamp),用于夯实计算机基础。
层次二:保持敏锐,追踪技术动态(碎片化输入)
这是为我的知识树添砖加瓦的“枝叶”。
- 技术社区与资讯平台:保持前沿
-
- 途径: GitHub、掘金、Stack Overflow、V2EX。
- 怎么做:
-
-
- GitHub: 每天会看 Trending 榜单,了解有什么新库、好项目诞生。我会 Star 优秀的项目,并阅读其源码学习设计思想。
- 掘金: 浏览高质量的技术文章,关注官方账号和业界大佬,了解最佳实践和行业动态。
-
- 优质 Newsletter 与技术博客:信息过滤
-
- 途径: JavaScript Weekly、Node Weekly、阮一峰的网络日志、以及一些国外团队(如React, Vite核心团队)的博客。
- 为什么: 信息过载的时代,这些渠道帮我做了高质量的信息筛选,让我能用最少的时间获取最精华的内容。
层次三:动手实践,从输入到输出(核心关键)
这是将知识内化的最重要一环。
- “做”项目:从模仿到创造
-
- 个人项目: 我不会只停留在看教程。当我学到一个新特性(比如 Vue 3 的 Composition API),我会立即创建一个小的 Demo 项目来亲手实践。
- 复盘与重构: 对于做过的项目,我时常会回顾,思考“如果现在重写,我会如何优化?”(例如:能否用 TypeScript 重构?能否用更好的状态管理?)这个过程让我进步飞快。
- 贡献开源与阅读源码:向高手学习
-
- 途径: GitHub。
- 怎么做: 我会从修复一些知名开源库的简单 typo 或文档错误开始,逐步尝试去解决一些
good first issue。更重要的是,我会选择一些我日常使用的、代码优雅的库(如Vueuse)去阅读其源码,学习其架构设计和代码规范。
层次四:交流分享,巩固与反馈
这是检验和提升学习成果的最终环节。
- 写作与分享:费曼学习法
-
- 途径: 写技术博客、在团队内做技术分享。
- 为什么: “教是最好的学”。当我尝试把一个问题清晰地讲给别人听时,我必须彻底理解它。写作和分享的过程能暴露我的知识盲点,促使我进一步查漏补缺。
- 与人交流:融入圈子
-
- 途径: 参加线下技术沙龙、与公司的前辈和同事交流、在技术群里讨论问题。
- 为什么: 交流能帮我打破信息茧房,获得不同的视角和思路。
总结
以官方文档和经典书籍为根基,以动手实践为核心驱动,并通过技术社区和写作分享来保持敏锐和巩固知识的立体化模式。在这个快速发展的领域,持续学习的能力比当前掌握的知识更重要。”
(11)开发过程中遇到的坑,怎么解决的?
面试官想考察的不是你从不犯错,而是你的问题解决能力、复盘总结能力和抗压能力。回答这个问题的关键在于:将一个“坑”转变为一个展示你能力的“故事”。
下面我将为你提供一个结构化的回答框架、一个高价值的示例,以及一些备选的“坑”和回答要点。
回答框架(STAR 原则)
用 STAR 原则来组织你的回答,让故事清晰有力:
- Situation: 简要描述项目背景和你的角色。
- Task: 你当时要完成的具体任务是什么?
- Action: 你具体做了什么来排查和解决问题?(这是重点,要体现你的思考过程)
- Result: 问题最终如何解决?你从中学到了什么?后续如何预防?
注意事项
- 诚实,但要有策略: 不要说自己没踩过坑。选择一个真实的、你已经解决的、并且能体现你学习能力的问题。
- 强调过程和成长: 重点不是你掉进了坑里,而是你如何爬出来的,以及你如何在坑边立了警示牌。
- 技术深度: 尽量选择一个有技术含量的坑,能展示你对原理的理解。
- 规避“低级错误”和“态度问题”: 不要讲因为粗心大意(如单词拼写错误)导致的坑,也不要讲因为和同事吵架导致的坑。
- 积极收尾: 故事的结尾一定要落在“我学到了什么”和“我如何做得更好”上。
更多推荐
所有评论(0)