前端面试题大白话(最全)(持续更新中......)
🌟 前端面试题大白话解析
一、 HTML & CSS 基础
1. 什么是盒模型?(必问)
大白话解释:把网页上的元素想象成一个快递箱。
- content(内容):里面的商品。
- padding(内边距):保护商品的防震泡沫。
- border(边框):纸箱子本身。
- margin(外边距):这个快递箱跟其他快递箱之间的距离。
🌟 面试得分点:提到 box-sizing: border-box。标准盒模型设置的宽度是“商品”的宽度,加上边框 and 泡沫会把盒子撑大;而设置了 border-box 的盒子,设置的宽度就是“整个纸箱”的宽度,无论怎么加泡沫,总体积不变(这在布局时最好用)。
2. 怎么让一个元素水平垂直居中?
大白话解释/答案:
- 最推荐(Flexbox):给父元素加三个属性:
display: flex; justify-content: center; align-items: center;。(就像把东西拽到弹性盒子的正中间)。 - 定位法(绝对定位):子元素
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);。(先走到屏幕一半,再往回退自己身长的一半)。
3. CSS 选择器 div.classSel 和 div .classSel 的区别?
专业术语回答:
div.classSel(没有空格)是交集选择器。表示选择既是div标签,同时自身又带有classSel这个类名的元素。div .classSel(有空格)是后代选择器。表示选择所有div元素内部(包含子元素、孙元素等任何层级)带有classSel类名的元素。
大白话解释:
- 没有空格
div.classSel(强调身份叠加):就像你在找**“戴着红帽子的男人”**。你要找的这个人,他必须【首先是个男人(div)】,而且【他自己头上戴着红帽子(.classSel)】。这两个条件必须长在同一个人身上。 - 有空格
div .classSel(强调包含关系):就像你在找**“男厕所里的红帽子”**。你要找的是红帽子(.classSel)这个东西,但它必须放在【男厕所(div)】这个大房间里面。不管是谁拿着它,只要在这个空间区域里就行。
二、 JavaScript 核心
1. 什么是闭包 (Closure)?(必问)
大白话解释:就像一个自带小背包的函数。正常情况下,一个函数执行完了,里面的局部变量就被销毁了。但如果函数里面还套着一个小函数,并且小函数用到了外面的变量,那么这个变量就不会被销毁,而是被装进“小背包”里一直带着。
- 优点:好处是可以用来保护私有变量(别人改不到)。
- 缺点:坏处是用多了背包太重,会导致“内存泄漏”(内存占着不释放)。
2. 函数防抖 (Debounce) 与 节流 (Throttle)
专业定义:两者都是为了限制函数的执行频率,优化高频事件(如
scroll、resize、input)带来的性能损耗。
① 防抖 (Debounce)——“等最后一个人说完再执行”
- 原理:当事件被触发后,延迟 n 秒再执行。如果在过程中事件再次被触发,则重新计时。
- 核心术语:定时器、闭包、延迟执行。
- 应用场景:搜索框联想(避免每输入一个字就发请求)、窗口尺寸调整 (resize)。
// 手写防抖(核心版)
function debounce(fn, delay) {
let timer = null; // 维护一个定时器标识
return function(...args) {
// ...args 叫做 剩余参数 (Rest Parameters)。
// 每次点击都清除上一次的定时器
// 理由很简单: 因为防抖函数是一个“包装盒”。 你不知道被包装的那个原始函数 fn 到底需要接收几个参数。
if (timer) clearTimeout(timer);
// 开启新的定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
② 节流 (Throttle)——“固定CD,按需释放”
- 原理:在一个规定时间内,只能触发一次函数。即使再次触发,也不执行。
- 核心术语:时间差计算、高频限制、恒定频率。
- 应用场景:疯狂点击提交按钮、滚动加载 (scroll)、鼠标移动 (mousemove)。
// 手写节流(时间戳版本)
function throttle(fn, delay) {
let lastTime = 0; // 上次执行的时间
return function(...args) {
let now = Date.now();
// 如果当前时间 - 上次执行时间 > 设定的 CD 时间,则执行
if (now - lastTime > delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
3. 什么是事件循环 (Event Loop)?宏任务与微任务?
大白话解释:JS 面试最爱问的机制。你可以把 JS 想象成去银行办业务,并且只有一个窗口(单线程)。
- 同步代码:你自己直接去窗口办。
- 异步代码:旁边拿号排队。
- VIP通道(微任务):比如
Promise.then。 - 普通通道(宏任务):比如
setTimeout。
- VIP通道(微任务):比如
- 执行顺序:当前窗口的业务办完,柜员会先叫光所有 VIP 通道的人,然后再去叫普通通道的第一个人。
4. 原型和原型链是什么?
专业术语回答:
原型(Prototype)是 JavaScript 中对象继承特性的基础。每个 JS 对象都有一个隐藏的[[Prototype]]属性(通常通过__proto__访问),指向它的原型对象。当尝试访问一个对象的属性时,如果对象本身没有,就会去它的原型对象上找,如果还没有,就继续找原型的原型,这就是原型链(Prototype Chain),直到找到null为止。
大白话解释:
就是**“找祖宗”**的过程。
- 比如你想借一辆法拉利(访问属性),你自己没有,你就去找你爸借(找原型对象)。
- 你爸要是也没有,你爸就去找你爷爷借,直到最后找到那个“老祖宗”(
Object.prototype)。如果在老祖宗那里也没找到,那最后就会告诉你“找不到”(返回undefined)。
💡 核心概念解析:
- 显式原型 (prototype):每一个函数(除箭头函数外)都有一个
prototype属性,指向一个对象。这个对象就是所有该构造函数产生的实例的公共祖先,存储了共享的方法。 - 隐式原型 (proto):每一个对象(除
null外)都有一个__proto__内部属性,它指向创建该对象的构造函数的prototype。 - 构造函数与 new 过程:当你执行
new Person()时,JS 引擎会创建一个空对象,并将这个新对象的__proto__强行指向Person.prototype,从而建立了继承关系。 - 原型链 (Prototype Chain):就是通过一个个
__proto__属性将对象、它的原型、原型的原型串联起来的一条寻找属性的路径。
📌 总结要点:
- 原型链是对象通过原型实现属性和方法继承的一种机制。
- 每个对象都有一个
__proto__属性,指向它的原型对象。 - 每个函数(包括构造函数)都有一个
prototype属性,指向一个对象,这个对象的属性和方法可以被实例共享。 - 构造函数创建对象时,新对象的
__proto__属性指向构造函数的prototype对象。 - 继承可以通过设置原型对象实现,也可以使用 ES6 的
class语法糖。
关于构造函数:
构造函数是用来创建对象的函数。通过 new 关键字调用构造函数,会创建一个新的对象,并将这个对象的 __proto__ 属性指向构造函数的 prototype 对象。
// 代码示例
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.__proto__ === Person.prototype); // true
三、 ES6 新特性
1. var、let、const 的区别是什么?
专业术语回答:
var:存在变量提升,没有块级作用域约束,可以重复声明。let:不存在变量提升,有块级作用域(由{}包裹的范围),存在暂时性死区,不可重复声明。const:跟let类似,但它声明的是常量,一旦声明,内存地址不能被修改。
大白话解释:
var(熊孩子):它可以不守规矩(到处乱跑,可以在声明之前就去用它而不报错),甚至你可以给它取好几次相同的名字,非常容易搞乱程序。let(守规矩的学生):必须先打招呼(先声明)才能用,而且只在自己的班级(括号{}里)活动,同一个班不能有两个同名的学生。如果你要改它的值,是可以的。const(办了铁证的学生):跟let性格差不多,但一旦上交了身份证(赋了初值),名字和基本信息就绝对不能改了。
2. 箭头函数和普通函数有什么区别?
专业术语回答:
- 语法更简洁。
- 箭头函数没有自己的
this,它会捕获其所在上下文的this值作为自己的this。- 不绑定
arguments对象,可以用...rest参数代替。- 不能作为构造函数使用(不能使用
new关键字发起),也没有prototype属性。
大白话解释:
- 普通函数(变色龙):它的
this是动态的,谁调用它,它里面的this就指向谁。这就容易造成你以为this是你自己,结果一不小心变成了别人(比如在setTimeout里变成window对象)。 - 箭头函数(死心眼):它是个没有自我的“死心眼儿”。它里面的
this,就是它出生时身边的那个对象,以后不管谁再怎么叫它,它心里那个this都不会变了。
3. Promise 是什么?(必问)
专业术语回答:
Promise是 ES6 提供的一个用于处理异步操作的对象。它代表了一个目前还不可用,但未来某个时间点会被解析或拒绝的操作。它有三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。状态一旦改变,就不会再变。
大白话解释:就像是在餐厅点餐拿到的小票/叫号器。
- Pending(进行中):你刚付完钱拿到等位小票,这会儿菜还没做好。
- Fulfilled(已成功):叫号器响了,你的菜做好了,你可以端走了(去执行
.then里面的事情)。 - Rejected(已失败):厨师跑出来说抱歉,没食材了,菜做不出来了(去执行
.catch里面的事情)。
4. 什么是解构赋值 (Destructuring)?
专业术语回答:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值。这不仅提高了代码的简洁性,还便于从 JSON 或复杂对象中快速提取深层结构的数据。
大白话解析:就像是**“按需取货”**。以前你想拿对象里的几个属性,得写好几行代码去点(.)出来;现在你只需要写个“框”,只要名字对得上,里面的东西就会自动跳进你的变量里,省时省力。
// 1. 对象解构(最常用、最推荐)
const user = { name: "小明", age: 18, job: "前端" };
// 现在:像个“模具”一样直接扣出来
const { name, job } = user;
console.log(name, job); // 小明 前端
// 2. 数组解构
const numbers = [100, 200, 300];
const [a, b] = numbers; // a=100, b=200
// 3. 骚操作 1:交换变量值(面试官很爱看这个)
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1(不用再定义中间临时变量了!)
// 4. 骚操作 2:函数参数解构(实战利器)
// 直接解开传进来对象,只拿需要的参数
function welcome({ name, age }) {
console.log(`欢迎 ${name},你今年 ${age} 岁了吗?`);
}
welcome(user);
5. 剩余参数 (Rest) 与 展开运算符 (Spread) 的区别?
专业术语回答:虽然都用
...符号,但场景相反。
- Rest(收集):用在函数参数中,把多个零散参数聚合成一个数组。
- Spread(摊开):用在数组/对象中,把一个整体拆开变成零散的每一项,常用于浅拷贝或合并对象。
大白话解析:- Rest 是“收货员”:把客户退回来的零散货(参数)全收进一个大麻袋(数组)里。
- Spread 是“摆地摊”:把麻袋里的东西(数组)全部摊在地上,让大家看清楚每一项。
6. ES6 的 Set 和 Map 是什么?
专业术语回答:
- Set:类似于数组,但成员的值都是唯一的,没有重复的值。常用于数组去重。
- Map:类似于对象,也是键值对集合,但“键(Key)”的范围不限于字符串,对象、函数甚至任何类型都能当键。
大白话解析:- Set 是“查重机”:丢进去的东西,重复的一律只留一个,特别适合做名单查重。
- Map 是“超级货架”:普通的货架(对象)只能用名字(字符串)找东西,Map 可以让你用任何东西(比如一张员工证)来当索引找东西,更灵活。
7. 什么是 Async/Await?(实战解析版)
专业术语回答:
async/await是 ES2017 引入的异步编程终极解决方案。它是 Generator 函数和 Promise 的语法糖。它将异步逻辑的写法“同步化”,极大地提升了代码的可读性与维护性。
💡 核心实例解析:
async function getPictrue() {
await getCode(); // 等待获取短信验证码
// 下面的逻辑(假设有)必须等 getCode 跑完才能执行
}
大白话解析(看这一遍就懂):
async(报备):函数前加async是告诉 JS 引擎:“这个函数里可能有耗时操作,我可能会点暂停,你先去忙别的,不用管我。”await(暂停键):执行到await getCode()时,函数内部会立刻原地停住,死死卡在这一行,直到getCode()这里出结果(也就是 Promise 变为已完成状态)。- 主线程不卡顿:虽然函数内部“停”了,但外面的世界(UI 渲染、按钮点击等)依然是活的,不会导致网页假死。
- 拿结果(解除暂停):一旦
getCode()返回了结果,函数会自动**“解除暂停”**,接着按顺序往下跑。
8. Promise 与 Async/Await 的深层关系与对比
async/await 是 Promise 的语法糖,本质上还是 Promise,只是写法更像同步代码,更好读。
🏗️ 核心关系:地基与大楼
- 底层依赖(地基):Promise 是地基。它是异步操作的状态管理器(Pending/Fulfilled/Rejected)。如果没有 Promise,
Async/Await根本无法记录和传递异步状态。 - 高级封装(大楼):Async/Await 是大楼。它是在 Promise 之上构建的高级语法糖。它本身并不产生异步状态,而是通过**“消费(读取)”** Promise 的输出来实现线性的逻辑运行。
🔄 “闭环”转换机制
- 出:每一个
async函数执行后,都会隐式返回一个 Promise(即使你 return 的是个普通值,也会被自动包装)。 - 进:在
async函数内部,await后面通常跟着一个待结算的 Promise。
| 维度 | Promise (.then) | Async/Await |
|---|---|---|
| 代码流向 | 链式异步感。逻辑被分散在各个 .then 回调中,层级深了易碎。 |
线性同步感。代码从上到下逻辑连贯,像写故事。 |
| 错误处理 | 链式捕获。通过 .catch() 捕获。 |
原生捕获。通过 try...catch 块一并管理。 |
| 并发粒度 | 高效聚合。通过 Promise.all 极大提升并发效率。 |
注意串行。若写多个 await 会导致变慢,仍需配合 Promise.all。 |
| 调试体验 | 略差。断点会在块间跳跃。 | 完美。调试断点单步走,体验等同同步代码。 |
🚀 核心对比案例与总结
1. Promise 写法(.then 链式)
function getData() {
return fetch('/api')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err))
}
2. async/await 写法(线性同步风格)
async function getData() {
try {
const res = await fetch('/api')
const data = await res.json()
console.log(data)
} catch (err) {
console.error(err)
}
}
📊 核心差异点总结:
| 对比维度 | Promise (.then) | async/await |
|---|---|---|
| 技术本质 | 原生异步对象/状态容器 | 基于 Promise 的语法糖 |
| 书写风格 | 链式调用,.then() 链接 |
线性书写,同步逻辑直观 |
| 错误处理 | 使用 .catch() |
使用原生的 try / catch |
| 嵌套逻辑 | 容易出现 then 链过长/碎片化 | 零嵌套,结构清晰无感 |
| 并行请求 | Promise.all() 实现 |
仍需配合 Promise.all() |
| 调试难度 | 调用栈信息散乱 | 极其友好,线性断点单步走 |
| 执行结果 | 显式返回 Promise | 隐式返回 Promise |
Promise 是基础,async/await 是它的优雅写法;
用 async/await 写异步代码,就像写同步代码一样舒服。
四、 TypeScript 基础 (TS)
1. TS 相比 JS 有什么优势?(为什么要用 TS?)
专业术语回答:TypeScript 是 JavaScript 的超集框架,主要增加了静态类型系统和支持 ES6 甚至更高版本的语法。优势在于:
- 编译时进行强类型检查,能够提前发现隐藏的类型错误。
- 提供强大的 IDE 智能提示和代码补全,提高开发体验。
- 代码即可作为文档,提高了项目的可维护性,特别适合大型前端项目。
大白话解释:可以把 JS 当作**“裸奔”,把 TS 当作“穿上带有警报器的衣服”**。
- JS:一个变量一会儿装数字,一会儿装字符串,在写代码的时候谁都不管,等到代码跑起来在用户的电脑上出了错才报错,直接引发线上事故。
- TS:自带一个“严格的老太太”,在你写代码敲键盘的时候,只要你把用来装数字的盒子塞了一头猪进去,她立马给你标个红色的波浪线骂你。虽然写起来费劲了点(要多写类型定义),但以后团队合作,谁也不敢瞎改代码了。
2. TS 里 type 和 interface 的区别是什么?
专业术语回答:
interface(接口):主要用于定义对象的形状(Shape),支持继承(extends)和声明合并(同名 interface 自动合并)。type(类型别名):可以定义任何类型,包括基础类型、联合类型、交叉类型、元组等。不支持声明合并,主要通过交叉类型(&)来实现扩展。
大白话解释:
interface(办会员身份):这主要是用来描述一个“东西”长什么样(比如规定一个用户必须要有名字和年龄)。你可以办了一张卡后,再去给这张卡增加新权益(多次声明同名 interface,会自动拼接在一起)。type(取外号/小名):什么都能管,它可以描述对象,也能指代基础类型(比如给string | number取个小名叫MyID)。但它是一次性的,取了名就定死了,后面别人想强行给这个名字加东西是加不进去的。
3. 什么是泛型 (Generics)?
专业术语回答:泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。它允许客户端在使用时提供类型参数,从而实现组件或函数的复用,且保持类型安全。
大白话解释:就像是一个占位用的“任意门”或“扭蛋机”。
- 想象你写了个函数叫“处理内容并返回”。如果不用泛型,你要么写死只能传数字进数字出,要么用
any(变成彻底不管的垃圾桶)。 - 用了泛型(通常用一个
<T>表示),就相当于说:“我现在不知道你会往我这塞什么类型,但我保证:你塞什么类型进来(比如传进来一只猫),我就原封不动返回什么类型出去(返回那只猫)”。
4. TypeScript 适合应用在什么场景中?
专业术语回答:
TypeScript 尤其适合以下场景:
- 大中型前端项目开发:需要长期维护、多人协作的项目,TS 的类型系统可以显著提高代码可读性和可维护性,降低重构成本。
- 复杂业务逻辑与状态管理:比如在使用 Vuex/Pinia 或 Redux 时,强类型能准确约束状态树的解构和变化。
- 基于现有框架的组件库/工具库开发:TS 可以自动生成
.d.ts类型声明文件,为使用者提供完美的 IDE 提示,如 Element Plus、Ant Design 都是基于 TS 重写的。
大白话解释:
- 人多、项目大(建高楼):如果是自己一个人写个小网页做测试,用 JS 随便写写就行(盖个茅草屋)。但如果是团队好几十号人一起开发一个要用好几年的大系统(建摩天大楼),为了防止张三把李四写的功能改崩了,或者过了半年你自己都忘了这个变量代表啥,就必须得用 TS 先把“施工图纸和规矩”定好。
- 写公共工具或组件(造轮子):如果你写了一个很牛的工具包给别人用,用 TS 写的话,别人在调用你的工具时,编辑器(比如 VSCode)就会像个贴心小助手一样,自动提示他应该传什么参数,少传一个都不让跑,这就叫“类型提示”。使用者体验极佳。
5. TypeScript 的语法长什么样?(跟 JS 相比)
大白话解释:TS 最核心的语法其实主要是**“冒号(
:)加类型”**。你把它当成 JS 的升级版,只是在声明变量或者函数的时候,多加了个冒号来“立规矩”。
举个通俗的例子对比一下:
【原来的 JavaScript 代码】(谁都可以乱传参数)
// JS 中写个函数,只要传进参数就行,不管是数字还是字符串
function add(a, b) {
return a + b;
}
add(1, 2); // 结果是 3(正常运行)
add("1", "2"); // 结果是 "12"(变成了字符串拼接,这在大型项目里是个定时炸弹)
【加上 TypeScript 后】(有了门禁系统)
// TS 中,用冒号 `: number` 把参数的类型强制卡死,只能是数字!
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // 结果是 3(正常运行)
// add("1", "2"); // ❌ 这行代码在写的时候(还没运行),编辑器就会直接标红报错!告诉你字符串不能当数字用!
【再看个对象的例子(使用 interface)】
// 1. 先立规矩:定义一个“用户”必须满足什么条件
interface User {
name: string; // 必须有名字,且是字符串
age: number; // 必须有年龄,且是数字
isVIP?: boolean; // 加个问号 `?`,代表这个可有可无,是布尔值
}
// 2. 按规矩办事:创建一个具体的用户对象,必须符合上面的规矩
const myUser: User = {
name: "张三",
age: 18
// isVIP 没写也不会报错,因为它加了问号 ?
// 但如果你写了 age: "十八",这里马上就会爆红警告!
};
6. JS 和 TS 的基本数据类型分别有哪些?(常考基本功)
专业术语回答:
- JavaScript 的数据类型分为两类:
- 基础类型(原始类型)(7种):
String(字符串)、Number(数字)、Boolean(布尔)、Null(空)、Undefined(未定义)、Symbol(ES6新增的唯一值)、BigInt(突破精度限制的大整数)。- 引用类型:
Object(对象,里面还细分为Array数组、Function函数等)。- TypeScript 的数据类型:完全包含了 JS 的上述所有类型!并且为了更严格的代码规范,额外新增了以下几种特殊类型:
any、unknown、void、never、Tuple(元组)、Enum(枚举)。
大白话简析 TS 新增的类型:
any(垃圾桶/任意型):相当于放弃 TS 检查,退化回 JS 的“裸奔”状态。啥都能装,写多了整个项目就变成 “AnyScript”,能不用就别用。unknown(未知的盲盒):它是any的安全版。它也是啥都能装进来,但你想拿它去用(比如进行运算或调它的方法)的时候,TS 会拦住你,逼着你先写一个if判断它到底是个啥(类型收窄),确认身份后才能用。void(打白工):一般用在函数上。意思是这个函数干完活就完了,不给你返回任何东西(没有return值或者只return undefined)。never(无底洞):表示永远不会有结果的类型。一般用在那种一执行就会抛出异常报错的函数,或者死循环永远出不来的函数上。Tuple(元组/定制版数组):普通的数组你爱塞多长塞多长,但元组是你规定死了它有多长,且每个位置是什么类型。比如[string, number],那就只能是一个字符串跟着一个数字,哪怕你多塞一个或者位置颠倒都不行。Enum(枚举/下拉菜单):把几个固定的选项列出来。(比如星期一到星期日,或者订餐系统里的“待支付、已发货、已完成”)。它能防止你手抖拼错字符串。
7. 什么是类型断言 (Type Assertion)?
专业术语回答:
类型断言是开发者告诉 TypeScript 编译器:“我比你更清楚这个变量的具体类型,请按我的意愿来处理”。它通过as语法或<类型>语法实现,仅在编译阶段起作用,不会进行实际的类型转换。
大白话解析:
- 就像是“刷脸进门”:TypeScript 非常严谨,如果它不确定一个变量是什么,它就不让你调用这个变量的方法。
- 作用:这时候你对编译器说:“别瞎猜了,我保证这就是个字符串(
as string),你就按字符串来办吧!”。 - 注意点:这只是在“欺骗”编译器,如果运行的时候它真的不是字符串,程序还是会崩。所以这叫“断言”,也就是你个人打包票承担责任。
8. 什么是类型守卫 (Type Guard)?
专业术语回答:
类型守卫是 TypeScript 中用于在运行时执行检查,以缩减 (Narrowing) 变量类型范围的机制。在条件语句中,如果检查通过,TypeScript 会在后续块中自动推断出更具体的类型。常用手段包括:typeof、instanceof、in关键字,以及自定义类型谓词 (Type Predicates)。
大白话解析:
- 就像是“身份核验员”:如果一个变量可能是字符串,也可能是数字(
string | number),直接用.length编译器会报警。 - 作用:只要你通过代码写了判断(比如
if (typeof val === 'string')),在大括号内,编译器就被这道“守卫”给说服了,它就允许你把变量百分之百当成字符串来使用,这就叫类型收窄。
9. 请对比解释 typeof、instanceof、in 这三个核心关键字
专业术语回答:
typeof:一元操作符。用于检测变量的基本数据类型(如string,number,boolean,undefined,symbol,bigint,function)。instanceof:用于检测一个对象是否属于某个构造函数(类)的实例。它通过追踪原型链来完成检测。in:用于检测对象(及其原型链)中是否包含某个特定的属性(Key)。
10. TypeScript 中的可选参数、默认参数及特殊符号有哪些?
专业术语回答:
- 可选参数 (
?):在参数名后添加?,表示该参数可传可不传,其类型会被推断为T | undefined。- 默认参数 (
=):直接在参数定义时赋初值,若未传入该参数,则使用默认值。- 剩余参数 (
...):使用展开运算符将多个零散参数聚合成一个数组。- 非空断言 (
!):告诉编译器该变量绝对不会为null或undefined。
大白话解析(小白通关篇):
?(可选参数:可有可无):就像是“选配”。比如setProfile(name, age?),年龄爱填不填,不填也不会报错。=(默认参数:保底方案):就像是“低保”。比如function pay(price = 100),如果你不给钱,系统就默认你付了 100 块。...(剩余参数:一网打尽):就像是“大麻袋”。不管你传多少个参数进来,我全装进这个袋子(数组)里。!(非空断言:我保证有值):就像是“免检章”。有些地方编译器怀疑可能是空的(null),你给它加个!,它就不敢再报警了。
11. TypeScript 和 JavaScript 的核心区别是什么?(必背对比)
专业术语回答:
- 类型管辖:JS 是 Dynamic Typing(动态类型),运行时确定类型;TS 是 Static Typing(静态类型),编译阶段进行类型核验。
- 语法特权:TS 是 JS 的 Superset(超集),在 JS 核心语法基础上集成了 Interface、泛型(Generics)、装饰器等强类型约束。
- 错误前馈:JS 的错误通常在 Runtime(运行时) 暴发;TS 通过 Compile-time(编译时) 检测,提前规避了 80% 的类型隐患。
大白话解析:
- JS 是“盲打”:写代码时没人管,出事了都在上线后。
- TS 是“智能纠错码”:写错的一瞬间,编辑器就标红报错,把事故扼杀在摇篮里。
- 比喻:JS 是毛坯房,TS 是精装带全屋监控的智能房。
五、 Vue 框架相关(针对你当前的项目)
1. Vue 2 和 Vue 3 的核心区别是什么?(总结版)
专业术语回答:
- 响应式系统优化:由基于
Object.defineProperty的响应式系统切换为基于 ES6Proxy。解决了 Vue 2 无法检测属性动态添加/删除、数组下标索引及 length 变化的问题,且内存开销更低。- 逻辑组织(Composition API):引入组合式 API,通过
setup函数将相关功能的逻辑聚合。解决了 Vue 2 Options API 在逻辑复用(如 Mixins 的来源不明问题)及逻辑碎片化方面的痛点。- 编译优化与性能:实现了“静态提升(Static Hoisting)”、“补丁标记(Patch Flags)”及“树摇优化(Tree Shaking Support)”。不仅首屏加载更快,且虚拟 DOM 的 Diff 过程更智能。
- 新特性支持:原生支持
Fragments(碎片,允许组件有多个根节点)、Teleport(瞬移,跨 DOM 渲染)、Suspense(处理异步组件加载状态)。- 核心架构:基于 TypeScript 重构,提供第一类 TS 全量支持;通过模块化重构将内核体积减至最小值。
大白话解析:Vue 3 就像是把原来的“老作坊(Options API)”升级成了“现代模块化工厂(Composition API)”,跑得更快、更省油(性能高、体积小)。
① 响应式原理(最底层、最重要的区别)
- Vue 2(守株待兔的门卫):使用的是
Object.defineProperty。它在初始化时,必须死板地给对象的每个属性都装上“监控”。缺点:如果你后来偷偷给对象加个新属性,或者直接改数组的某一项,门卫是发现不了的(所以得用$set)。 - Vue 3(全身都是传感器的管家):使用的是
Proxy。它直接监控整个对象。不管你往对象里加属性还是删属性,管家都能瞬间察觉。优点:性能更好,且完美支持数组和动态添加属性。
② 逻辑编写方式(API 的进化)
- Vue 2(按部门排队:Options API):你的代码必须放在固定的地方。数据放
data,方法放methods,计算属性放computed。当一个功能(比如搜索)代码很多时,你需要在这几个地方来回跳跃,找得头晕。 - Vue 3(按功能分组:Composition API):引入了
setup。你可以把“搜索功能”的所有数据和逻辑写在一起,把“分页功能”再写在一起。代码整块抽离,像搭积木一样,复用起来超级爽。
③ 性能优化(跑得更快了)
- 静态提升(Hoist Static):Vue 3 很聪明。如果你代码里有一大段是固定不变的文字(静态节点),Vue 3 只会把它创建一次,以后更新页面直接复用。
- 体积更小(Tree Shaking):Vue 3 把不常用的功能拆散了,如果你项目中没用到某个功能,打包时它就不会被包含进去,项目体积变得很轻盈。
④ 其他新特性
- 多个根节点(Fragments):Vue 3 不需要外层非得套一个
<div>了,多个标签直接并列排在一起也没问题。 - TypeScript 支持:Vue 3 是原生用 TS 写的,适配极好;而 Vue 2 对 TS 的支持像是“强行凑对”。
- Teleport(传送门):可以将组件渲染到 DOM 树之外的其他地方(比如弹窗直接传送到 body 下)。
2. Vue 的双向绑定原理是什么?(必问)
大白话解释:Vue 在底层给你请了一个**“管家”(数据劫持 + 发布订阅)**。
- 在 Vue 2 里是
Object.defineProperty,Vue 3 里是Proxy。 - 这个管家死死盯着你的数据,只要数据一变,管家马上跑去通知页面更新;反过来,如果在输入框里打字(页面变了),管家也会把数据修改掉。
3. v-if 和 v-show 有什么区别?
大白话解释:都是用来隐藏和显示东西的。
v-if是“杀伐决断”:如果为false,直接把这个节点从页面上撕掉(销毁),为true再重新建一个。耗性能,适合不怎么切换的场景。v-show是“隐身衣”:不管怎样节点都在,只是给它穿了件伪装服(加上了 CSS 样式display: none)。适合频繁切换显示隐藏的场景。
4. computed(计算属性)和 watch(侦听器)的区别?
大白话解释:
computed像“果汁机”:丢进去几样水果,榨出一杯果汁(根据已有数据算出一个新数据)。它有缓存,只要水果不变,你一直要果汁,他就把刚才榨好的直接给你,不用重榨。必须有 return。watch像“监控摄像头”:死盯着某一个数据,只要这个数据一发生变化,摄像头就会报警并执行一段你写好的代码(比如去发个网络请求)。不需要 return。
5. 组件之间怎么传值?
大白话解释:
- 父传子:老爸给儿子发零花钱(用 Props)。
- 子传父:儿子给老爸打电话汇报成绩(用
$emit触发自定义事件)。 - 兄弟传值: Vuex / Pinia(找个公共的存钱罐),或者 EventBus(Vue3 废弃了)。
6. 什么是插槽(Slot)?插槽具体怎么用?
大白话解释:插槽就像是买了个带凹槽的专属手机壳(你的子组件)。手机壳是加工好的,但中间那个洞到底露出来的是苹果 logo 还是华为 logo,取决于你往里塞什么手机(父组件传入的 HTML 内容)。
插槽主要分为 3 种用法:
① 默认插槽(就挖一个坑)
相当于买了个最基础的透明后盖,随便你往里面塞一张什么照片都能显示。
- 子组件 (
<MyCard>):在需要留坑的地方写一对<slot></slot>。 - 父组件:在调用子组件的标签内部,直接写你要塞进去的内容。
<!-- 子组件 MyCard.vue -->
<div class="card">
<slot>默认提示:如果老爸没塞东西,我就显示这段话</slot>
</div>
<!-- 父组件 -->
<MyCard>
<!-- 塞进坑里的红字 -->
<span style="color: red;">🔥 今天有大促!</span>
</MyCard>
② 具名插槽(挖多个坑,按名字对号入座)
相当于买了个精装版盒子,上面专门挖了用来放戒指的坑(叫 title),和放贺卡的坑(叫 footer)。不写名字就会乱套,所以必须要通过 v-slot:名字 或者简写 #名字 来对号入座。
<!-- 子组件 MyLayout.vue -->
<div>
<h2> <slot name="header"></slot> </h2>
<main> <slot></slot> <!-- 等于 name="default" --> </main>
<footer> <slot name="footer"></slot> </footer>
</div>
<!-- 父组件 -->
<MyLayout>
<!-- 用 #名字 告诉 Vue,我要把这行文字塞进哪个坑里 -->
<template #header> <h3>这是头部标题</h3> </template>
<!-- 没写名字的,全都掉进默认坑位 main 里 -->
<p>这是中间大段大段的正文内容...</p>
<template #footer> 底部版权信息 </template>
</MyLayout>
③ 作用域插槽(也是面试高级考点:子传坑父用)
大白话:坑是挖在子组件的,数据也是属于子组件的(从没抛给外界)。但是,老爸(父组件)偏偏要用子组件私有的数据,来决定怎么渲染这个坑里的样式。所以,子组件在挖坑的时候,顺便把自己的数据绑在坑边上,让老爸“借去”用一下。
<!-- 子组件 MyList.vue (它自己内部有个私有的数组) -->
<template>
<div v-for="item in list" :key="item.id">
<!-- 挖坑的同时,把 item 绑在坑上的 row 属性上交出去 -->
<slot name="itemSlot" :row="item"></slot>
</div>
</template>
<!-- 父组件 -->
<MyList>
<!-- 父组件接管了这个坑,并通过解构赋值 { row },拿到了子组件内部隐藏的 item 数据! -->
<template #itemSlot="{ row }">
<!-- 父组件就可以决定,遇到带有 VIP 的行就标红 -->
<span :class="{ 'red': row.isVip }">{{ row.name }}</span>
</template>
</MyList>
六、 计算机网络与浏览器
1. 什么是跨域?怎么解决?
大白话解释:浏览器有个“同源策略”(安全保安),如果你的网页 A(比如
localhost:8080)要去请求服务器 B(比如api.baidu.com)的数据,因为域名、端口、协议有任何一个不一样,保安就会把你拦住报错。
怎么解决:
- CORS:最常用,让后端大哥在服务器端设置一个允许通行证(Header 里加
Access-Control-Allow-Origin)。 - Nginx 反向代理:前端自己配个代理,让浏览器以为是发给自己的,实际上代理悄悄转发给了后端。(你在开发 Vue 时的
vue.config.js里的proxy就是干这个的)。
2. 从浏览器地址栏输入 URL 到页面显示,经历了什么?(经典长题)
大白话解释:(按照下面 4 步回答即可)
- 查电话本:DNS解析,把网址变成 IP 地址。
- 打电话确认:TCP 三次握手,跟服务器确认连接建立成功。
- 要数据:浏览器发送 HTTP 请求,服务器把 HTML/CSS/JS 发回来。
- 搞装修:浏览器像包工头一样,拿到代码开始渲染。先搭骨架(DOM树),再上漆(CSSOM树),最后把两者拼在一起画到屏幕上。
3. HTTP 和 HTTPS 的区别是什么?(必问)
专业术语回答:
- 安全性:HTTP 是明文传输,协议本身不加密;HTTPS 是在 HTTP 的基础上加了 SSL/TLS 层进行加密传输。
- 端口:HTTP 默认端口是 80;HTTPS 默认端口是 443。
- 证书(CA):HTTPS 需要向证书颁发机构申请数字证书,用于验证服务器身份;HTTP 则不需要。
- 三次握手基础:HTTPS 在 TCP 三次握手之后,还需要进行 SSL/TLS 的握手过程来交换对称密钥。
- SEO 排名:搜索引擎通常会给 HTTPS 网站更高的权重(排名分)。
大白话解释:
- HTTP(大喊大叫):就像你在闹市区大声喊出你的账号密码,周围所有人(中间人、黑客)都能听得清清楚楚。
- HTTPS(加密对讲机):你和服务器之间连了一个专属的对讲机,你们说的话会被自动搅碎成只有你们俩能听懂的代码。哪怕坏人偷听了,也只是一堆乱码。
- 证书(防伪标识):HTTPS 就像是官方认证的蓝 V 账号,能保证你访问的真的是银行官网,而不是骗子搭的假网站。
七、 实战笔试真题速记(必背)
这部分是从你提供的《题目.txt》中提炼的核心考点,非常适合面试前 10 分钟快速过一遍!
1. CSS 选择器与优先级
- 符号区分:无空格(交集,如
div.classSel表示既是div又有该类名)、空格(后代,包含孙子)、>(直接儿子)、+(紧挨着的弟弟)、~(后面的所有弟弟)。 - 优先级:
!important> 标签上的style>#ID>.类/伪类/属性>标签。同级别后面写的覆盖前面的。
2. 四种定位(Position)及包含块
static:默认躺平,不能动.relative(相对定位):不脱离文档流,以自己原本的位置为基准偏移。常用来给 absolute 当他爹。absolute(绝对定位):脱离文档流,以最近的非 static 祖先为基准。fixed(固定定位):脱离文档流,死死钉在浏览器视口上,屏幕怎么滚它都不动。
3. JS 奇葩的相等性比较(== vs ===)
==会偷偷转换类型再比,===值 and 类型必须完全一样。0 === -0(true),0 == false(true,因为 false 会被转成 0)。- 🌟
NaN谁都不认,包括它自己!NaN === NaNandNaN == NaN都是 false。判断 NaN 必须用Number.isNaN()。 null == undefined(true),但null === undefined是 false。
4. 数组与正则常用 API
- 找
NaN:用[NaN].includes(NaN)是 true,但用indexOf(NaN)会返回 -1(因为 indexOf 底层用的是严格相等===,而 NaN 不等于 NaN)。 - 一句代码英文单词首字母大写:用正则
replace(/\b\w/g, match => match.toUpperCase())。
5. TS 的类型收窄(Type Narrowing)
如果一个参数可能是好几种类型(比如 string | A | B | Array),你在使用前必须先判断清楚它是谁:
- 判断数组:
Array.isArray(obj) - 判断类的实例:
obj instanceof A - 判断 Interface 对象:因为 Interface 在编译成 JS 后就消失了(不能用 instanceof),所以只能用**“鸭子类型”**判断,即用
'value' in obj来判断它身上有没有这个特定属性。
6. 算法真题核心思路(别手写,说出思路即可)
- 斐波那契数列(1,1,2,3,5…):千万别用递归,会超时甚至栈溢出!要用**“迭代法”**(循环),只定义三个变量存前两项的值,不断往后滚,性能最高(时间 O(n),空间 O(1))。
- 机器人走方格(只能右/下):本质是一个排列组合数学问题(从 m+n 步里挑 m 步向右走),如果有障碍物一般用动态规划 (DP) 解决,公式是
当前格子走法 = 上面格子走法 + 左边格子走法。 - 污染海域(找未污染独立海域):这是 LeetCode 热题**“岛屿数量”。核心思路:双层循环遍历全图,只要遇到没被污染的(1),独立海域就
+1,然后立刻起一个病毒传染函数(DFS/BFS)**,把跟它相连的所有1全变成0(标记为走过,避免重复数)。
八、 浏览器、缓存与性能优化(高级加分项)
1. localStorage、sessionStorage 和 Cookie 的区别?
大白话解释:都是浏览器里面用来存东西的“小仓库”。
- Cookie(随身小纸条):容量极小(4KB),每次发起网络请求(比如去借书)都会自动带上这张纸条交给服务器。一般用来存你的“身份令牌(Token/SessionID)”。
- sessionStorage(临时存物柜):只要你不关掉当前浏览器标签页(柜门没关),存的东西就一直在。一旦关掉刚才那个网页,东西立刻被清空。适合存表单临时填入的数据或者只在这次访问中用的单页状态。
- localStorage(永久大仓库):容量大(5MB左右),只要你不手动点击清理缓存,存进去的东西就算关掉电脑明年再打开,它依然存在。适合存用户的搜索历史、主题颜色偏好(比如夜间模式)。
2. 什么是“重绘(Repaint)”和“回流/重排(Reflow)”?
专业术语回答:
- 回流:当 DOM 的变化影响了元素的几何信息(位置、尺寸大小),浏览器需要重新计算元素的几何属性,并将它们放置在正确的位置,这个过程叫回流。
- 重绘:当一个元素的外观发生改变,但没有改变布局(比如修改了颜色、背景),重新把元素画出来的过程叫重绘。
- 核心规律:回流必定触发重绘,而重绘不一定触发回流!回流的性能开销远大于重绘。
大白话解释:
- 回流(拆墙重盖):你把房子的承重墙拆了,或者给房间扩容了(改了宽高、内外边距、插入/删除了元素等),那整个房子的结构(布局)都要重新计算,这个动作特别累(耗手机性能)。
- 重绘(重新刷漆):你只是给房子换了个壁纸(改了文字颜色
color或背景色background),这个动作很快搞定。
3. 如何进行前端性能优化?(必考大题,加分项)
专业术语回答:
从网络层面、渲染层面两大类入手:
- 网络层面:开启 Gzip 压缩、静态资源放 CDN 上、合理使用强缓存与协商缓存、减少 HTTP 请求数。
- 渲染层面:减少 DOM 操作(能用一段 HTML 追加就不用循环一个个追加)、减少重绘与回流(CSS 动画尽量用
transform来代替top/left的变化,因为它在 GPU 层运行,不改布局)、图片懒加载(滚动到哪里加载到哪里)intersectionObserver API 还有loading等于lazy属性。- 工程化层面:代码分割(路由懒加载)、提取公共代码库(SplitChunks)。
- 图标logo类使用svg
大白话思路:基本就两点思路:“少拿点东西”(省网速),“少折腾浏览器”(省性能)。
-
- 大卡车(网络请求)要少拉空车:没翻到的长页面图片先别去下载它(懒加载),大家都要用的共同库包只打包一次(代码分割),拿过的网页模板直接从仓库拿不用重新请求服务器(走浏览器缓存)。
-
- 别折腾包工头(浏览器渲染引擎):别总让它“拆墙重盖(回流)”,做特效动画多用“加个滤镜和调个位置(用
transform/opacity加动画效果而不是狂改margin)”。因为transform有显卡(GPU)大爷罩着,不耗 CPU 性能!
- 别折腾包工头(浏览器渲染引擎):别总让它“拆墙重盖(回流)”,做特效动画多用“加个滤镜和调个位置(用
九、 Vue 高阶实战(组件封装与页面通讯)
1. Vue 组件之间到底有几种通讯方式?(全面版)
大白话讲解:这是必考题,能多背几种就多背几种,显得你有经验。
- 父子通讯:最常见。老爸给儿子发零花钱(Props),儿子向老爸汇报成绩(emit∗∗自定义事件)。如果老爸想直接强求儿子干活,可以直接用拿他把柄(∗∗emit** 自定义事件)。如果老爸想直接强求儿子干活,可以直接用拿他把柄(**emit∗∗自定义事件)。如果老爸想直接强求儿子干活,可以直接用拿他把柄(∗∗refs 或 vue3 的 defineExpose)。
- 祖孙通讯(跨级):爷爷直接把传家宝给孙子,绕过儿子。爷爷用 Provide(提供) 准备好东西,下面不管多少代的孙子直接用 Inject(注入) 即可拿到。
- 兄弟通讯:Vue3 推荐直接用大金库(Vuex/Pinia 全局状态管理)或者是用一个第三方的信鸽(比如 mitt 库充当 EventBus)。
2. Vuex/Pinia 全局状态管理的专业用法是什么?
专业术语回答:
在 Vue3 中,官方推荐使用 Pinia 来替代 Vuex。它主要是解决跨组件状态共享的问题。
Pinia 的核心概念有三个:
state:用来存储全局的数据状态。getters:类似于组件的 computed,用来对 state 里的数据进行计算过滤。actions:类似于组件的 methods,用来封装修改 state 的业务逻辑(无论是同步还是异步请求都在这里写,因为 Pinia 废弃了 Vuex 里的 mutations)。
大白话讲解与代码实战(以“购物车”为例):
想象我们要建一个存放大家都能访问的“购物车大金库”。
步骤 1:建立大金库(定义 Store)
// store/cart.ts
import { defineStore } from 'pinia';
// 采用 Vue3 组合式 API (Setup) 的写法定义 Store,非常直观!
export const useCartStore = defineStore('cart', () => {
// 1. state (也就是水箱里原本的水)
const cartList = ref(["苹果", "香蕉"]);
// 2. getters (对水箱里的水做个过滤处理,比如算重量)
const totalCount = computed(() => cartList.value.length);
// 3. actions (买台抽水机,专门用来往水箱里加水/抽水)
const addFruit = (fruitName: string) => {
cartList.value.push(fruitName);
}
// 把外头需要用的金库密码全部交出去
return { cartList, totalCount, addFruit };
});
步骤 2:兄弟组件 A(菜单栏)往金库里加东西
<template>
<button @click="buy">加入购物车</button>
</template>
<script setup>
import { useCartStore } from '@/store/cart';
// 拿到金库大门的钥匙
const cartStore = useCartStore();
const buy = () => {
// 调用金库的 actions(抽水机)直接往里加东西
cartStore.addFruit("西瓜");
}
</script>
步骤 3:兄弟组件 B(顶部购物车图标)自动更新数量
<template>
<!-- 当兄弟 A 点完按钮,这里会自动变成 3,完全不需要父组件传话! -->
<div>购物车总数量:{{ cartStore.totalCount }}件</div>
</template>
<script setup>
import { useCartStore } from '@/store/cart';
// B 组件也拿到自己的钥匙,就可以直接看见变化
const cartStore = useCartStore();
</script>
如果你引入了 Pinia 之后,A 组件 and B 组件甚至都不在一个页面里,也不需要通过谁传话,全靠他们手里握着的这把 useCartStore 的钥匙,看着同一个公共大黑板!
3. 什么是 Pinia?(Vue3 推荐)
大白话解释:Pinia 是 Vue 的“全局储物柜”。
想象你在盖一栋大楼(Vue 项目),每个房间(组件)都有自己的小抽屉,但有些东西(比如用户信息、Token、主题颜色)是每个房间都要用的。这时候你就需要一个放在走廊尽头的“大柜子”,谁想用谁就去拿,谁改了大家都能看到最新版本。
- 存什么:用户信息、登录状态(Token)、全局配置(语言、皮肤)。
- 优势:跨页面共享数据超级简单,不用一层层传参数。
4. 为什么要用 Pinia?(Pinia vs 不加 Pinia)
- 如果不加 Pinia:数据只能在自己组件里。如果要传给几个层级之外的页面,得像接力赛一样一层层传(Props),或者通过复杂的事件总线(EventBus),代码乱且难维护。
- 如果加了 Pinia:数据统一存放在 Store 里。任何页面想拿、想改,直接引入对应的 Store 即可,就像直接在共享文档上改字一样方便。
5. Pinia 的常见用法
- State:也即数据源。
- Getters:计算属性。
- Actions:同步 or 异步修改数据的方法。
11. Pinia vs Vuex 的区别(必背对比)
一句话总结:Pinia 是 Vuex 的进化版,更简单、更轻巧、对 TypeScript 支持更好。
💡 核心区别对比表
| 特性 | Vuex (旧版/Vue2) | Pinia (新版/Vue3) |
|---|---|---|
| Mutations | 必须有(很麻烦) | 已删掉,合并到 Actions 中 |
| 异步操作 | 只能在 Actions 里做 | 同步异步都写在 Actions 里 |
| 层级结构 | 嵌套 Module 很复杂 | 扁平化设计,多个 Store 自动关联 |
| TS 支持 | 非常差,需要写很多类型定义 | 原生支持,智能提示极强 |
| 体积 | 较重 | 极轻(约 1KB) |
| 推荐度 | 新项目不推荐 | 官方唯一推荐 |
🔥 Vuex 为什么麻烦?(代码对比)
Vuex 流程:Dispatch (Action) -> Commit (Mutation) -> Modify State
(改个数据要跳三跳,还要记两种不同的触发方式 commit/dispatch)。
Pinia 流程:Call Action -> Modify State
(跟调用普通函数一样简单,代码更清爽)。
12. 页面与页面之间(跨路由)怎么通讯(传递数据)?
大白话讲解:页面 A 跳转到页面 B,要带着数据过去。
- URL 传参(贴小纸条):在地址栏后面挂着走(比如
?id=123)。适合传个短小精悍的 ID。缺点是刷新页面虽然在,但不够隐蔽,传不了复杂大对象。 - 全局状态池(Vuex/Pinia):所有页面共享的“中央大水箱”。A 页面倒进去,B 页面接水喝。缺点是一旦用户按 F5 刷新了网页,如果没做持久化(存在 Storage 里),数据就全没了。
- 本地存储(localStorage / sessionStorage):把大块数据硬塞进浏览器的“临时/永久柜子”里。A 页面存进去,B 页面拿出来。非常靠谱,刷新不丢。
13. 如果让你封装一个 Vue 组件,你会怎么做?
这道题往往是面试官在拷打你的“真实项目经验”。能不能写出能在团队复用的好代码。
专业术语回答:封装组件的核心原则是**“单向数据流”、“高内聚低耦合”、“预留扩展插槽”**。
【以封装一个通用的“带清空功能的漂亮搜索框组件 (MySearch.vue)”为例】:
- 第一步:定规矩(Props 进,Emit 出)—— 坚决不直接改外面的数据!
- 第二步:留后门(加插槽 Slots)—— 保持极高的扩展性
- 第三步:藏私货(内部状态闭环与暴露)—— 高内聚,不管别人的闲事
14. Vue2 和 Vue3 的生命周期钩子有哪些?有什么区别?(必背基础)
大白话讲解:如果把一个 Vue 组件当做“一个人”,那生命周期就是他从**出生(创建)、长大(挂载)、生病换新衣服(更新)、死亡(销毁)**的全过程在不同时刻触发的“闹钟”。
【Vue2 的生命周期(传统 Options API)】:就是成双成对出现的 8 个钩子:
- 创建时:
beforeCreate、created。 - 挂载时:
beforeMount、mounted。 - 更新时:
beforeUpdate、updated。 - 销毁时:
beforeDestroy、destroyed。
【Vue3 的生命周期(组合式 API / Composition API)的区别】:
- 最大变化 1:没有
beforeCreateandcreated啦!因为setup函数本身就代替了这两步。 - 最大变化 2:命名更直观。
mounted变成了onMounted,销毁相关改名为onBeforeUnmountandonUnmounted。
💡 临门一脚小建议:
如果你在面试时遇到不会的题,千万不要直接说“我不会”,可以这么说:
“这个具体的知识点我平时用的不多,但我遇到类似问题时的解决思路是根据官方文档 or 搜索… / 我在某个项目里用过类似的……”
祝你面试大捷! 🎉
Webpack 打包流程:读配置 → 找入口 → 分析依赖 → loader 编译 → plugin 优化 → 输出 dist
Tree Shaking = 摇树优化就是 Webpack 在打包时,把项目里没用到的代码 “摇掉”,只保留用到的代码,减小打包体积。
原理(超简单)
只支持 ES6 模块化(import / export)
不支持 CommonJS(require)
构建时静态分析代码
删掉未引用的函数、变量、模块
幽灵依赖 = 你没在 package.json 里声明,却能直接引用的依赖。
举个例子
你项目只装了 vue,没装 axios,但因为某个包里依赖了 axios,你居然能直接 import axios from ‘axios’ 并运行。这就是 幽灵依赖。
十、 手写代码系列:发布订阅模式 (Pub/Sub Pattern)
专业术语解析:
发布订阅模式是一种消息范式,它将消息的发送者(发布者)与接收者(订阅者)解耦。发布者不需要知道订阅者的存在,两者通过一个中间层的“事件通道”进行异步通信。
class EventBus {
constructor() {
// 使用 Map 数据结构作为事件注册表 (Event Registry)
// 键为事件名称 (Type),值为回调函数数组 (Callback Queue)
this.eventMap = new Map();
}
/**
* 订阅 (Subscribe)
* @param {string} eventName 事件标志
* @param {Function} callback 回调函数
*/
on(eventName, callback) {
// 惰性初始化:若不存在该事件队列,则新建数组
if (!this.eventMap.has(eventName)) {
this.eventMap.set(eventName, []);
}
// 将订阅者的回调推入队列
this.eventMap.get(eventName).push(callback);
}
/**
* 发布 (Publish / Dispatch)
* @param {string} eventName 事件标志
* @param {any} data 传递给回调的负载数据 (Payload)
*/
emit(eventName, data) {
if (this.eventMap.has(eventName)) {
// 迭代执行对应事件的所有观测者 (Observer) 回调
this.eventMap.get(eventName).forEach(callback => callback(data));
}
}
/**
* 取消订阅 (Unsubscribe)
* 必须传入订阅时的原始函数引用才能正确移除
*/
off(eventName, callback) {
if (this.eventMap.has(eventName)) {
const callbacks = this.eventMap.get(eventName);
const index = callbacks.indexOf(callback);
if (index !== -1) {
// 利用 splice 进行原地删除,减少空间开销
callbacks.splice(index, 1);
}
}
}
}
🌟 面试高级进阶考点:
- 解耦 (Decoupling):
- 核心回答:发布订阅模式消除了发布者和订阅者之间的硬编码依赖。在 Vue 或 React 跨组件通信场景中,组件无需知道谁在接收消息,极大地提升了系统的可维护性和扩展性。
- 内存泄漏风险 (Memory Management):
- 核心回答:在组件化开发(如 Vue
beforeUnmount或 ReactuseEffect清理函数)中,必须显式调用off移除监听器。否则,由于EventBus对象持有回调函数的引用,垃圾回收机制 (GC) 无法回收已销毁组件的内存。
- 核心回答:在组件化开发(如 Vue
- 为什么选择
Map而非普通对象{}:- 核心回答:
Map的查找、插入复杂度为 O(1)O(1)O(1),且直接提供了has、get等原型方法,语义化更强。此外,Map的键可以是任何类型,而对象的键会被强制转换为字符串。
- 核心回答:
- 单向数据流违背?:
- 核心回答:虽然 EventBus 方便,但过度使用会导致数据流向变得难以追踪(“幽灵通信”)。在大规模状态管理中,推荐使用 Pinia/Redux 这种更具规范性的状态机。
1. 响应式原理(最底层、最重要的区别)
专业术语回答:
- Vue 2:基于
Object.defineProperty。通过遍历对象属性,使用defineProperty劫持getter和setter。局限性:无法检测对象属性的动态添加/删除,以及数组通过索引赋值的操作。- Vue 3:基于 ES6
Proxy。通过代理整个对象,拦截所有属性的访问操作(包括读取、写入、删除等)。优势:能够完美支持对象属性的动态操作和数组的索引赋值,且性能开销更低。
大白话解释:
- Vue 2(守株待兔的门卫):使用的是
Object.defineProperty。它在初始化时,必须死板地给对象的每个属性都装上“监控”。缺点:如果你后来偷偷给对象加个新属性,或者直接改数组的某一项,门卫是发现不了的(所以得用$set)。- Vue 3(全身都是传感器的管家):使用的是
Proxy。它直接监控整个对象。不管你往对象里加属性还是删属性,管家都能瞬间察觉。优点:性能更好,而且完美支持数组和动态添加属性。
2. 逻辑编写方式(API 的进化)
大白话解释:
- Vue 2(按部门排队:Options API):你的代码必须放在固定的地方。数据放
data,方法放methods,计算属性放computed。当一个功能(比如搜索)代码很多时,你需要在这几个地方来回跳跃,找得头晕。- Vue 3(按功能分组:Composition API):引入了
setup。你可以把“搜索功能”的所有数据和逻辑写在一起,把“分页功能”再写在一起。代码整块抽离,像搭积木一样,复用起来超级爽。
3. 生命周期钩子(闹钟改名了)
大白话解释:
- 核心逻辑没变,但长相变了。Vue 3 的生命周期前面都加了个
on,并且要在setup里引入。- 最大的坑:Vue 3 里的
setup直接取代了原来的beforeCreate和created。- 改名表:
beforeDestroy变成了onBeforeUnmount,destroyed变成了onUnmounted。
4. 多个根节点(Fragments)
大白话解释:
- Vue 2(必须有个大纸箱):模板里面必须有一个
<div>包住所有内容,否则会报错。- Vue 3 (散装也行):不需要最外层那个多余的
<div>了,多个标签直接并列排在一起,页面结构更清爽。
5. 性能优化(跑得更快了)
大白话解释:
- 静态提升(Hoist Static):Vue 3 很聪明。如果你代码里有一大段是固定不变的文字(静态节点),Vue 3 只会把它创建一次,以后更新页面直接复用,不像 Vue 2 每次都要重新走一遍流程!
- 体积更小(Tree Shaking):Vue 3 把不常用的功能拆散了,如果你项目中没用到某个功能,打包时它就不会被包含进去,项目体积变得很轻盈。
6. TypeScript 支持
大白话解释:
- Vue 2 对 TS 的支持像是“强行凑对”,写起来很别扭。
- Vue 3 是直接用 TS 写的,天生完美适配。现在由于项目越来越大,Vue 3 + TS 已经是大厂开发标配。
🔥 Hooks 的核心作用(必背版)
① 解决逻辑复用难题 (Logic Reuse) —— 最大亮点
专业术语回答:
在 Hooks 出现之前,React 很难在组件之间复用状态逻辑(常见的做法是使用 HOC 高阶组件 或 Render Props,但这会导致包裹地狱)。Hooks 引入了 Custom Hooks(自定义钩子) 机制,能将逻辑从组件中彻底抽离,实现跨组件的高度复用。
大白话解析:
- 就像给组件装上一个“U盘”:以前你想给几个组件都加一个“自动刷新数据”的功能,得把整个组件代码改一遍;现在只需要写一个共享的小钩子,谁想用直接“插上”一行代码就行。
十二、 Angular 框架简述 (选看/了解)
1. 什么是 Angular?它和 Vue/React 有什么区别?
专业术语回答:
- 定义:Angular 是由 Google 开发的一款基于 TypeScript 的开源 Web 应用框架。它采用了完整的 MVVM(Model-View-ViewModel) 架构。
- 全家桶设计 (Battery-included):与 React 只专注于“视图层”不同,Angular 是一个重量级的、全功能的框架。它内置了路由(Routing)、状态管理、表单处理(Forms)、HTTP 客户端等所有常用功能。
- 核心特性:强依赖 Dependency Injection(依赖注入) 和 RxJS(响应式编程库)。
大白话解释/小白专享:
- Vue 是“精装修的小公寓”:住进去很方便,东西也都齐全,适合大多数人(上手快)。
- React 是“毛坯房”:只给你一个坚实的框架,墙、地板、灯都要你自己去买喜欢的品牌(灵活性极高)。
- Angular 是“重型坦克”或“大型航空母舰”:它非常沉、非常厚重,学习成本很高。但它一旦开动起来,稳定性和规矩是最好的。它强制要求你必须按它的规矩写(必须用 TypeScript、必须用装饰器),这让多人协作的大型项目非常整齐。
2. Angular 的优势和劣势分别是什么?
大白话解析:
- 优势(极佳的规范性):每个人写的代码看起来都一模一样。因为它有很多强制的模版和规范,非常利于几百个人在一起协作,不会写乱。
- 劣势(太难学啦):新手小白看到那些“RxJS”、“Observable”、“依赖注入”会直接劝退。而且由于它功能太多,打包出来的文件体积也比 Vue/React 大。
十四、 Node.js 服务端开发 (核心面试题)
1. 什么是 Node.js?它和 JavaScript 有什么区别?
专业术语回答:
- 定义:Node.js 是一个基于 Chrome V8 引擎 的 JavaScript 运行时(Runtime)。它不是一门语言,也不是框架,而是一个让 JS 可以运行在浏览器之外的环境。
- 核心区别:JS 主要用于浏览器端的 DOM/BOM 操作;Node.js 主要用于服务端,提供了文件系统(fs)、网络请求(http)、进程管理(process)等 系统级 API。
大白话解析:
- 浏览器 JS 是“宅男”:它只能在浏览器这个小房子里玩,不能碰电脑里的文件和硬盘。
- Node.js 是“户外版 JS”:它给 JS 插上了翅膀,让它能在服务器上跑,直接读写数据库、删改文件。它们用的是同一副“大脑(JS语法)”,但 Node.js 拥有更强大的“双手(系统能力)”。
2. Node.js 为什么快?请解释“单线程、非阻塞 I/O”。
专业术语回答:
Node.js 采用了 Event Loop(事件循环) 机制和 Libuv 库 提供的非阻塞 I/O 模块。这使得它在处理高并发请求时,不需要为每个请求创建新的线程,极大地节省了系统开销。
大白话解析/经典比喻(必背):
- 传统服务器(多线程,比如旧版 PHP):就像一个小餐馆,来一个客人(请求)就雇一个服务员。客人多的时候,服务员没地方站(内存占满),且大多数时间服务员都在等厨师炒菜(阻塞),效率低。
- Node.js(单线程非阻塞):整个店里只有一个全能服务员。客人点完餐,他把菜单给厨师,立刻去接待下一个客人。等厨师炒好菜,铃声一响(事件回调),他再去把菜端给对应的客人。只要厨师(系统底层)够快,这一个服务员就能同时应付几百个客人。
3. 什么是 Node.js 的中间件 (Middleware)?
大白话解析(以 Express 为例):
- 中间件就像是“关卡”或“安检”:当一个请求发往服务器时,它要经过好几个“安检员”。第一个安检员看你有没有登录,第二个安检员记录一下你的进门时间,第三个安检员帮你把带的包(参数)整理好。最后,由于每个安检员都盖了章(调用
next()),你才能顺利见到“最终负责人(业务代码)”。
十五、 工程化与构建工具 (Webpack vs Vite)
1. Webpack 和 Vite 的核心区别是什么?为什么 Vite 这么快?
专业术语回答:
- 开发模式原理:Webpack 是 Bundle-based(基于打包) 的机制。它需要先抓取所有模块,构建依赖图,打包完成后才能启动服务器。
- Vite 原理:Vite 利用了浏览器原生的 ES Modules (ESM) 特性。它在开发环境下是 No-bundle(不打包) 的,利用浏览器去解析
import,服务器只在浏览器请求时按需编译文件。- 热更新速度 (HMR):Webpack 的热更新速度会随着项目规模增大而变慢(O(n)O(n)O(n));Vite 的热更新速度是常数级的(O(1)O(1)O(1)),无论项目多大,速度几乎不变。
- 底层构建工具:Vite 在预构建阶段使用 Go 编写的 Esbuild,速度比 JavaScript 编写的 Webpack 转换器快 10-100 倍。
大白话解析(小白必看):
- Webpack 是“开餐厅先做饭”:你还没进门,他得把菜单上所有的菜(所有文件)都洗好、切好、炒好放在盘子里(打包成一个大文件),最后才开门营业(服务器启动)。所以项目越大,你要等越久。
- Vite 是“现点现做”:他开门很快。你进去点个“番茄炒蛋(一个文件)”,他才立刻去后厨给你炒。你没点的菜(没用到的文件),他碰都不碰。所以无论菜单多长,开门速度都飞快。
- 比喻:Webpack 像是一个严谨的老师傅,慢工出细活,兼容性无敌;Vite 像是一个闪电侠,快到离谱,但在一些老旧环境(IE等)需要额外操心。
十六、 算法基础与复杂度分析 (加分必备)
1. 什么是大 O 表示法 (OOO)?O(1)O(1)O(1)、O(n)O(n)O(n)、O(n2)O(n^2)O(n2) 有什么区别?
专业术语回答:
- 定义:大 O 表示法用于描述算法的 时间复杂度 (Time Complexity),它表示随着输入数据量 nnn 的增长,算法执行时间的增长趋势。
- O(1)O(1)O(1) - 常数时间:执行时间不受数据量影响(如:访问数组索引)。
- O(n)O(n)O(n) - 线性时间:执行时间与 nnn 成正比(如:单层
for循环)。- O(n2)O(n^2)O(n2) - 平方时间:嵌套循环带来的指数级增长,效率较低。
- O(logn)O(\log n)O(logn) - 对数时间:二分查找带来的极高效率,数据倍增但步数只加一。
大白话解析:
- O(1)O(1)O(1)(瞬间移动):不管屋子里多少人,我都知道 3 号位是谁。
- O(n)O(n)O(n)(逐个排查):人越多,我一个个找的时间就越久。
- O(n2)O(n^2)O(n2)(互相握手):每个人都要和其他所有人握手,人一多简直忙死。
- O(logn)O(\log n)O(logn)(砍半快修):每次都砍掉一半不符合的数据,效率逆天。
2. 在代码中如何快速识别复杂度?(实战举例)
① O(1)O(1)O(1) —— “精准定位”
function getFirst(arr) {
return arr[0]; // 无论数组多大,拿第一个的时间永远一样。
}
② O(n)O(n)O(n) —— “层层过滤”
function findTarget(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) return i; // 单层循环,取决于 n 的大小。
}
}
③ O(n2)O(n^2)O(n2) —— “嵌套爆发”
function findDuplicates(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (i !== j && arr[i] === arr[j]) return true; // 循环套循环,性能杀手!
}
}
}
④ O(logn)O(\log n)O(logn) —— “对半劈砍”
while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) low = mid + 1; // 范围直接缩减一半!
else high = mid - 1;
}
十七、 前端性能瓶颈:内存泄漏 (Memory Leak)
1. 什么是内存泄漏?它是怎么产生的?
专业术语回答:
- 定义:内存泄漏是指程序中已动态分配的堆内存由于某种原因未释放或无法释放,造成系统内存的浪费。在 JS 中,通常是因为 Garbage Collector (GC) 垃圾回收机制 无法正确回收那些不再被引用的对象。
- 常见成因:
- 意外的全局变量(始终挂在
window上)。- 被遗忘的定时器(
setInterval没调clearInterval)。- 闭包的滥用(内部函数持有外部作用域的大对象,导致该对象无法释放)。
- 脱离 DOM 的引用(DOM 节点被删除了,但 JS 变量还留着它的指针)。
- 未移除的事件监听器。
大白话解析(小白必看):
- 就像是“借书不还”:
电脑内存就像一个图书馆。JS 会自动帮你把看完的书归还(垃圾回收)。 - 内存泄漏的情况:
- 定时器不关:就像是你订了一个“每秒响一次”的闹钟,哪怕你离开房间了,它还在响。
- 全局变量:就像是你把书焊死在桌子上了,管理员想搬也搬不走。
- 脱离 DOM:就像是你把书架上的书抽出来了,但你手里还紧紧攥着它。图书馆系统以为这本书还在用,其实它已经在角落里落灰了。
- 后果:书(内存)被占得越来越多,新来的书没地方放,最后整个图书馆(网页)就瘫痪(卡死/崩溃)了。
2. 如何排查内存泄漏?(Chrome DevTools 实战)
大白话解析:
- 第一招:看任务管理器:按
Shift + Esc,如果发现网页占的内存一直在涨,从不回头,那稳稳地是泄漏了。 - 第二招:堆快照 (Heap Snapshot):在 Chrome 开发者工具的
Memory选项卡里点三次按钮。如果你发现某个对象在“快照 1”里有,到了“快照 3”还在,这就是“捉拿归案”的有力证据。
十八、 Web 安全与实战防御 (大厂必考)
1. 什么是 XSS 攻击?如何防范?
专业术语回答:
XSS (Cross-Site Scripting) 跨站脚本攻击。攻击者通过在网页中注入恶意脚本,使其在用户的浏览器中执行,从而盗取 Cookie 或 Session。
防范手段:
- 输入过滤/转义:对用户输入的
<、>等特殊字符进行转义。- Content Security Policy (CSP):通过设置安全策略,只允许浏览器加载指定来源的资源。
- HttpOnly:给敏感 Cookie 设置
HttpOnly标志,让 JS 无法通过代码读取。
大白话解析:
- 就像是“往酒里下毒”:坏人在你的评论区留了一段代码
<script>偷你账号</script>。如果你没做处理就直接显示出来,你的用户一打开页面,这段代码就自动运行了。 - 解药:把
<变成<,把毒药变成普通的文字显示出来。或者给酒瓶(Cookie)加个只能看不能摸的盖子(HttpOnly)。
2. 什么是 CSRF 攻击?如何防范?
专业语回答:
CSRF (Cross-Site Request Forgery) 跨站请求伪造。攻击者利用用户已登录的状态,伪造用户的名义发送恶意请求。
防范手段:
- CSRF Token:服务器给页面发一张“专属入场券”,每次请求都必须带着它。
- SameSite Cookie:设置 Cookie 属性,禁止第三方网站携带本站 Cookie。
大白话解析:
- 就像是“偷刻公章”:你登录了银行网站,没关掉。这时你点了一个羞羞的广告链接,广告网站背地里发了一个“给坏人打钱”的请求给银行。银行一看:“哎?这是你的正规公章(Cookie)啊!”于是钱就打出去了。
- 解药:每次办业务,银行都得问你要一个一次性的动态口令(Token)。坏人虽然能偷到你的章,但他拿不到这个新口令,请求就作废了。
十九、 针对当前岗位的定向面试题 (重点突破)
这部分是根据你提供的岗位描述 (JD) 特别准备的,面试官大概率会从这些实际工作场景出发进行考察。
1. UniApp 开发 H5 项目有哪些“坑”或注意事项?(JD 提到 UniApp)
专业术语回答:
- 视口适配:需要注意移动端 100vh 在某些浏览器(如 Safari)中包含工具栏的问题。
- 跨端差异:使用
uni-api时需注意某些 API 仅支持小程序而不支持 H5。- 导航与路由:H5 端存在浏览器的“后退”物理按键,需要处理好
onBackPress生命周期。- 条件编译:熟练使用
#ifdef H5进行代码隔离。
大白话解析:
- 就像是“量体裁衣”:UniApp 是可以一套代码写小程序和 H5,但 H5 是运行在浏览器里的。
- 坑点 1:浏览器有地址栏和底部的返回箭头,这会占屏幕高度,你设置
height: 100%可能导致内容被挡住。- 坑点 2:有些功能(比如扫码、支付)在微信小程序里两行代码搞定,但在普通 H5 浏览器里可能需要调不同的 JS-SDK。
- 必杀技:一定要学会用
#ifdef H5 ... #endif。这就像是给代码贴标签:“这段只有在变成网页时才生效,小程序里请无视我”。
2. 后端接口报错(如 404 或 500),你作为前端怎么排查?(JD 涉及接口调试)
大白话解析(排查 4 步走):
- 第 1 步:看控制台(Network):先看请求发出去没有?地址(URL)对吗?参数(Payload)对吗?如果地址写错了(比如多了个斜杠),那就是前端的锅。
- 第 2 步:看状态码:
- 404:地址不存在(通常是后端没上线这个接口,或者你拼错单词了)。
- 500:后端代码崩溃了(别犹豫,立刻截图报错信息找后端大哥修)。
- 401/403:权限问题(可能是 Token 过期了或没登录)。
- 第 3 步:看数据格式:如果接口通了但是报错,看返回的 JSON。很多时候后端会返回
{ code: -1, msg: "余额不足" }。- 第 4 步:用 Postman 测一下:如果 Postman 也不通,那百分之百是接口本身的问题;如果 Postman 通了但网页不通,那就是你代码里的请求头(Headers)或者跨域(CORS)没配好。
3. 你平时是怎么利用 AI Coding 工具(如 Cursor, Copilot)的?(JD 核心要求)
大白话解析(展现你是一个“AI 高手”):
- 不能盲目复制:AI 生成的代码我必须先人肉审核一遍,看它有没有写死数据、有没有循环里套异步这种低级错误。
- 主要用法 1(写重复逻辑):比如我要写一个表单验证,正则太难记,我让 AI 直接生成,我只管测试效果。
- 主要用法 2(重构与解释):遇到一段看不懂的“屎山代码”,我丢给 AI 让它帮我分析逻辑、加注释、或者改成更易读的写法。
- 主要用法 3(单元测试):我给它一个函数,让它帮我生成各种边界条件的测试用例(JD 提到了单元测试)。
- 态度:AI 是我的**“高级助理工程师”**,它可以帮我快速生成代码,但逻辑的最终把关人永远是我。
4. 为什么要严格遵守 ESLint 和 Prettier 规范?(JD 提到团队规范)
大白话解析(体现职业素养):
- 防止“打架”:如果不统一规范,张三用空格,李四用 Tab,王五在行尾加分号。大家一起改同一个文件时,Git 提交记录里全是这种格式修改,根本看不出谁改了业务逻辑。
- 提前发现 Bug:ESLint 能在运行前就提示你:“喂,你定义了变量却没用!”或者“你这里可能导致内存泄漏”。
- 解放精神:配置好后,我只管写代码,按一下保存(Ctrl+S),代码自动按规范排版得整整齐齐,不用纠结大括号放哪行,把精力全放在业务上。
5. 什么是“前后端分离”开发模式?(JD 基础要求)
大白话解析:
- 以前(和面):后端写完代码直接把 HTML 吐出来,前端只是在里面加点样式。出了问题分不清是谁的。
- 现在(点菜):后端只负责“做菜”(也就是提供数据接口,返回 JSON)。前端负责“装修餐厅”和“上菜”(编写页面逻辑,通过 Ajax/Axios 去拿数据)。
- 优点:分工明确。后端坏了不影响前端页面显示,前端改了 UI 也不需要后端重写逻辑。大家共用一套“菜单(接口文档)”就行。
二十、 钛动科技前端实战真题复盘
这部分是根据最新的面试复盘整理的,重点考察了 React 深度、SEO 优化 以及 AI 协同开发。
1. React Fiber 是什么?为什么需要它?
专业术语回答:
- 定义:Fiber 是 React 16 引入的协调引擎,旨在实现增量渲染(Incremental Rendering)。
- 核心机制:它将同步的更新机制改为异步可中断任务。任务被拆分为小的 Work Unit,通过
requestIdleCallback在浏览器空闲时执行。- 优先级调度:Fiber 允许对不同任务设置优先级(如:动画 > 数据请求),确保交互流畅。
大白话解析:
- 以前(React 15):React 渲染就像是“搬家”,一旦开始搬,必须一口气干完,界面在这段时间里会卡死。
- 现在(React 16 Fiber):React 会把“搬家”拆成很多“小快递盒”。每搬一个盒子,就停下来问问浏览器:“大爷,你现在忙吗?有人要点击按钮吗?”。如果有紧急事,React 就先去处理,处理完再回来。
- 核心目的:解决大型项目的页面卡顿,让交互更丝滑。
2. SEO 优化主要有哪些手段?
大白话解析(必背 4 点):
- 语义化标签:别到处都用
<div>,标题用<h1>,侧边栏用<aside>。这就像给书加了清晰的目录。- 服务端渲染 (SSR):这是最有效的。普通的 Vue/React 网页蜘蛛抓不到关键信息。用 Nuxt.js 或 Next.js 直接生成完整的 HTML 发给搜索蜘蛛。
- 关键词配置 (TDK):配置好
Title、Description和Keywords。- 图片 Alt 属性:给图片加上文字描述,方便搜索引擎识别图意。
3. 如何使用 AI 辅助开发?如何验证 AI 代码的“坑”?
大白话实战经验:
- 怎么使用(AI 协作流):
- 定人设:告诉它:“你是一个资深前端专家”。
- 拆需求:不要写“写个网页”,要写“基于 Vue3 写一个带分页功能的表格组件,支持搜索和排序”。
- 补细节:让它增加单元测试,或者优化 TS 类型定义。
- 如何避坑(验证流程):
- 逻辑检查:检查它有没有写死数据(硬编码)、有没有逻辑漏洞。
- 边界测试:尝试输入空数据、特殊字符,看 AI 代码会不会崩。
- 安全检查:严防
v-html或dangerouslySetInnerHTML(易被 XSS 攻击)。
4. 跟 AI 对话实现一个组件的实操过程?
大白话思路(按 3 步走):
- 提架构:提供技术栈(Vue3/React + UI库),让它拆解成多个小模块。
- 写逻辑:详细描述交互,让它写具体的 JS/TS 业务函数。
- 跑通调优:运行代码,遇到 Bug 丢回给它,让它自行排查修复。
二十一、 实战真题 26 问全解 (最新复盘补全)
这部分内容直接对应你提供的图片中的 26 条面试真题,建议逐条背诵。
1. 自我介绍 (模板)
大白话建议:
- 开头:我是 [你的名字],目前大 [几] 在读,主攻 Vue3 + TS 技术栈。
- 重点:我有移动端 UniApp H5 开发经验,熟悉异步接口调优和组件化开发。
- 亮点:我不仅基础扎实,还擅长利用 AI 工具辅助开发,对团队代码规范(ESLint)和 Git 工作流有实际应用经验。
2. H5 相比之前新增了什么?
- 语义化标签:
<header>、<nav>、<section>、<article>、<aside>、<footer>。 - 本地存储:
localStorage(持久) 和sessionStorage(会话)。 - 多媒体:原生支持
<video>和<audio>播放。 - 图形处理:
Canvas(2D 绘图) 和SVG(矢量图)。 - 新 API:地理定位 (Geolocation)、离线存储、Web Workers (让 JS 支持多线程运算)。
3. WebSocket 有了解过吗?
- 回答:它是 HTML5 提供的全双工通信协议。
- 大白话:HTTP 是“一问一答”,你不问后端,后端不能说话;而 WebSocket 是“实时连麦”,一旦连上,后端可以随时主动给前端推消息。
- 场景:股票大屏、客服聊天、实时告警。
4. localStorage 和 sessionStorage 的区别?
- localStorage:只要你不删,关掉浏览器再打开数据还在(持久化)。
- sessionStorage:只要当前标签页或者是浏览器一关,数据就没了。
5. CSS 布局方式有哪些?
- 传统派:标准流、浮动布局 (
float)、定位布局 (position)。 - 现代派:Flex 布局 (目前所有项目的标配) 和 Grid 布局 (格状布局,适合最复杂的排版)。
- 响应式排版:百分比、Rem、Vw/Vh。
6. Flex 布局实现水平垂直居中?
display: flex;
justify-content: center; // 水平居中
align-items: center; // 垂直居中
7. ES6 相比 ES5 新载了哪些核心特性?
- 基础:
let/const、模板字符串、箭头函数。 - 工程化:
import/export模块化。 - 高效开发:结构赋值、扩展运算符 (
...)。 - 异步控制:Promise、Async / Await。
8. 箭头函数和普通函数有什么区别?
- this 指向:箭头函数没有自己的
this,它只在它被定义的地方捕获环境里的那个this。 - 构造能力:不能作为构造函数(不能
new),没有prototype。 - Arguments:没有自带的
arguments变量。
9. 箭头函数想拿到类似 arguments 的参数怎么办?
- 回答:写成
(...args) => {}。这个args是个真正的数组,不仅拿到了全部参数,还没了以前那个伪数组的臭毛病。
10. 你对 Promise 的理解?
- 回答:它是解决异步流程的一种优雅方案。有三种状态:进行中 (
Pending)、成功 (Fulfilled)、失败 (Rejected)。解决了传统回调导致的“回调地狱”。
11. Promise.race 有什么用?用过没?
- 回答:它是“谁快谁赢”。谁先返回结果(成功/失败),它就返回谁。
- 场景:做**“请求超时”**保护。拿网络请求跟一个 5 秒强制报错的定时器比赛,谁先跑完执行谁。
12. 事件循环中的宏任务有哪些?用户操作是什么任务?
- 宏任务:
setTimeout、setInterval、script整体代码、交互事件的回调。 - 微任务:
Promise.then。 - 用户操作:点击、键盘输入触发的回调函数,属于宏任务。
13. 原型链是什么?你是怎么看的?
- 回答:就是**“层层找祖先”**。每个对象都有个
__proto__内部指针,指向它的原型对象。如果访问一个属性没有,就沿着这条链子往上找,直到找到Object.prototype或者是null结束。
14. Vue3 和 Vue2 的核心差异点?
- 原理:Vue3 用
Proxy代替defineProperty,速度更快,支持动态加属性。 - 代码风:Composition API 代替 Options API,更容易拆分复用逻辑。
- 编译:多了静态提升,打包体积更小。
15. defineProperty 有实际了解过吗?(原理考题)
- 回答:它是 Vue2 的基石。拦截属性的
getter/setter。 - 缺点:如果你直接给对象加个新变量,或者用数组下标改值,它感知不到(所以得用
$set)。
16. computed 和 watch 的区别?
- computed:有缓存特性,用来“变魔术(把多项变一项新数据)”,且一定要
return。 - watch:没缓存,是个“盯梢的”,盯着一个数据变了,就去发请求或干别的。
17. watchEffect 用过吗?
- 差异:
watch必须要点名它盯着谁;watchEffect会自动搜查它内部用了谁。且初始化时会立刻跑一次。
18. defineModel 用过吗?(Vue 3.4 最新特性)
- 回答:它是 Vue 3.4 专门优化组件双向绑定的。
- 以前:要子传父再父传子,写一堆
emit。 - 现在:子组件写
const model = defineModel(),直接改model.value,父组件的数据同步变,太省事了。
19. v-model 语法糖的具体含义?
- 回答:本质是绑定了
:modelValue属性,并监听了@update:modelValue事件。
20. Vue 的组件通信方式有哪些?
- 父子:Props/Emit、
defineExpose/$refs。 - 跨级:Provide / Inject。
- 全局:Pinia、Mitt (事件总线库)。
21. Pinia 里面如何订阅 Store 变化?
- 回答:通过
$subscribe()方法。就像给整个购物车加了个监控,只要里面任何一件商品变动,都会触发它。
22. 路由守卫的场景?除了 beforeEach 还有什么?
- 全局:
beforeEach(鉴权锁)、afterEach(关掉加载动画)。 - 组件级:
beforeRouteLeave(离开页面前提示用户:数据还没保存呢,确定走吗?)。
23. & 24. TypeScript 中 type 和 interface 区别?
- interface:只能存对象,能继承 (
extends),能重名合并(拼积木)。 - type:万能外号。可以存对象,也能存联合类型(如
string | number)。但取了名字就不能再改(不支持重名合并)。
25. Git 常用命令与如何回滚?
- 常用:
git pull、git push、git status。 - 回滚 (重要):
git reset --hard [id]:抹去历史,强制回到过去。git revert [id]:生成一个新的、反向的提交,把错的那版覆盖掉,更稳妥。
26. 工作内容介绍,了解过 auto.js 吗?
- 回答:它是一个 Android 端的自动化辅助工具,主要是通过 JS 来模拟人的点击行为。如果你写过能体现你**“解决问题的思维”**。面试官问这个可能是关注你的代码在物理环境(如手机触控、自动化测试)的应用能力。
二十二、 首屏性能优化专项 (LCP/FCP 必考)
这道题主要考查你是否理解全链路的运行机制(从输入 URL 到页面渲染)。
1. 资源“瘦身”战术(减小体积)
- 路由懒加载:Vue/React 项目必做。配合
() => import(),让首屏只下载首页的 JS,不下载还没点开的页面。 - Tree Shaking:打包时自动剔除没用到的代码(死代码),减小包体积。
- 库的按需引入:不要全量引入 Element Plus 或 Ant Design。只引入首页要用的组件样式。
- 图片压缩与 CDN:大图全部上 CDN,首页图片尽量用 WebP 格式(比 PNG 小 30%)。
2. 网络“加速”战术(缩短传输)
- 开启 Gzip/Brotli 压缩:在服务器(Nginx)层开启。JS 文件能从 1MB 直接压缩到 200KB 左右。
- HTTP/2 多路复用:让浏览器能同时发上百个请求,不需要排队等待(解决 HTTP/1.1 的线头阻塞)。
- 浏览器缓存策略:合理设置强缓存(如静态资源)和协商缓存,让二次打开几乎是秒开。
3. 渲染“欺骗”战术(提升用户体感)
- 骨架屏 (Skeleton Screen):在真实数据还没回来前,先显示灰色方块占位。用户会觉得“已经在加载了”,减少焦虑。
- SSR (服务端渲染):终极方案。服务器直接吐出带内容的完整 HTML,浏览器直接画,无需等待 JS 下载和执行完再去调接口(极大提升 LCP 指标)。
- 关键 CSS 内联:把首页头部的 CSS 直接写在 HTML 里的
<style>标签中,减少一次 CSS 文件的网络请求。
4. 浏览器“省力”战术(减少阻塞)
- 脚本 defer/async:给非核心 JS 加上
defer,让它们在 HTML 解析后再执行,别挡着浏览器画首页。 - 预取/预加载 (Prefetch/Preload):利用
link rel="preload"告诉浏览器:“这个文件很重要,请优先帮我下载”。
💡 面试官加分点:
“我会通过 Chrome 的 Lighthouse 工具对首屏进行审计,重点关注 LCP(最大内容绘制时间,应在 2.5s 内)和 CLS(累积布局偏移,防止页面闪烁抖动)等核心指标。”
总结
- 资源瘦身:懒加载、Tree Shaking、WebP 图。
- 网络加速:Gzip 压缩、HTTP/2 多路复用、CDN。
- 体感优化:骨架屏、SSR 服务端渲染、关键 CSS 内联。
- 阻塞减少:Script 标签的 defer 属性、资源预加载。
二十三、 JS 进阶核心必杀技 (中高级岗必看)
这部分内容能拉开你与普通开发者的差距,展现你对 JS 底层运行机制的深度理解。
1. 什么是深拷贝 (Deep Copy) 和 浅拷贝 (Shallow Copy)?
- 浅拷贝(复制快捷方式):只拷贝一层。如果对象里嵌套了对象,新旧对象还是共用同一个地址。改了新对象里的嵌套项,原对象也会变。
- 常见手段:
Object.assign()、扩展运算符...。
- 常见手段:
- 深拷贝(克隆双胞胎):递归拷贝所有层级。新旧对象完全独立,互不影响。
- 常规手段:
JSON.parse(JSON.stringify(obj))(缺点:无法拷贝函数、正则、Date等)。 - 专业手段:手写递归,或者是使用
lodash的_.cloneDeep()。
- 常规手段:
2. call、apply、bind 有什么区别?
- 相同点:都是用来强制修改
this指向并借用别人的方法。 - 区别:
call:传参逐个传入:fn.call(obj, arg1, arg2)。立即执行。apply:传参以数组形式传入:fn.apply(obj, [arg1, arg2])。立即执行。bind:参数逐个传入,但它返回一个新函数,你想什么时候调用就什么时候调用。不立即执行。
3. JS 中的 this 指向逻辑口诀
- 默认绑定:函数普通调用,
this指向window。 - 隐式绑定:谁调用它,它就指向谁(如
obj.fn(),this指向obj)。 - 显式绑定:用
call/apply/bind强行指定谁,它就指向谁。 new绑定:this指向new出去的新实例对象。- 箭头函数:死心眼。它的
this是在定义时决定的(继承父级环境),以后不管谁调用它,它都不会变了。
4. 什么是“垃圾回收机制 (GC)”?
- 引用计数:数着书被多少人看过。如果一个人都没看(引用数为 0),就当废纸扔掉。
- 标记清除(主流):从大门(根对象,如
window)出发,能顺着引线走到的对象都贴个“还在用”的标签。最后,那些贴不上标签的对象(走不到的),统统回收内存。
5. 0.1 + 0.2 为什么不等于 0.3?
- 回答:因为计算机底层用二进制存储数字。
0.1和0.2在转成二进制时是无限循环小数,会有精度的微小丢失。加在一起就变成了0.30000000000000004。 - 方案:用
toFixed(1)或者是先把两边扩大 10 倍相加后再缩小 10 倍。
6. JS 事件循环 (Event Loop) 深度复盘(必刷真题)
执行模型:柜台与排队(VIP 先走,普通人拿号)
- 同步任务:直接在执行栈里干完。
- 微任务 (VIP):
Promise.then。所有微任务必须在当前宏任务干完后,下一个宏任务开始前,全部清空! - 宏任务 (普通号):
setTimeout、setInterval、脚本整体代码。每次只拿一个来执行。
🌟 典型输出顺序真题分析:
console.log('1'); // 1. 同步打印
setTimeout(() => {
console.log('2'); // 4. 异步宏任务
Promise.resolve().then(() => {
console.log('3'); // 5. 宏任务里的微任务
});
}, 0);
new Promise((resolve) => {
console.log('4'); // 2. 同步打印(构造函数内部是同步的!)
resolve();
}).then(() => {
console.log('5'); // 3. 微任务,在所有同步走完后执行
});
console.log('6'); // 同步打印
// 输出结果:1, 4, 6, 5, 2, 3
- 解析:先走同步任务(1, 4, 6),然后清空所有微任务(5),最后排队走宏任务。由于 2 运行完又往队列加了个 3,所以 2 之后要先清空在这个动作里产生的微任务 3。
二十四、 ES6 核心语法进阶 (变量声明与 Class)
这是前端开发的“地基”,面试官通过这些题判断你的基础是否扎实。
1. var、let、const 的核心区别?
| 特性 | var (旧时代) |
let (推荐) |
const (首选) |
|---|---|---|---|
| 作用域 | 函数级(容易变量污染) | 块级(只在 {} 内有效) |
块级(只在 {} 内有效) |
| 变量提升 | 允许(值是 undefined) |
不允许(暂时性死区报错) | 不允许 |
| 重复声明 | 允许(容易起冲突) | 禁止(同名直接报错) | 禁止 |
| 修改值 | 允许 | 允许 | 绝对禁止(不可重新赋值) |
大白话建议:
var是个“熊孩子”:不守规矩,现在项目里严禁使用。let是个“乖学生”:守规矩,只在自己的小圈子(大括号)里动。const是个“死脑筋”:一旦认定了第一个值,这辈子都不能变。优先使用const,只有确定变量会变才用let。
2. class 类和普通构造函数有什么区别?
- 语法糖:
class是对原型链继承的封装,写起来更像传统的后端语言(Java/C#),结构更清晰。 - 强制
new调用:class必须使用new关键字来创建实例,直接像函数一样调用会报错。 - 不可提升:普通函数可以先用后定义,
class必须先定义后使用。 - 逻辑组织:
class通过extends关键字实现继承,比以前手写Prototype指向要简单、安全得多。
二十五、 UniApp 跨端开发专项研究 (JD 核心点)
因为你的 JD 明确要求了 UniApp H5 开发,所以它的生命周期是必考点。
1. UniApp 的三层生命周期(应用、页面、组件)
- 应用生命周期 (App.vue):管理整个 App 的启停。
onLaunch:初始化。适合做全局登录检查。onShow/onHide:进入前台/隐藏到后台。
- 页面生命周期 (Page):管理单张页面的进出。
onLoad:页面加载。最重要的! 只能在这里拿路由传参,且发请求比mounted快一步。onReady:页面初次渲染完成。onPullDownRefresh:监听下拉刷新。onReachBottom:监听页面触底(常用在分页加载列表数据)。
- 组件生命周期 (Component):也就是 Vue 的那一套。
created、mounted、unmounted。
2. 面试必杀技:onLoad vs mounted 选谁?
回答策略:
优先选onLoad。因为它能拿到页面传来的 ID 参数(mounted拿不到),且触发时机更早,能让页面更早展示出数据,对用户体验更好。
3. UniApp 是如何实现本地存储数据的?
- 核心方法:
uni.setStorageSync(key, value)(同步) 和uni.getStorageSync(key)(同步)。 - 与原生
localStorage的差异点(加分项):- 跨端屏蔽:在 H5 中它调
localStorage,在小程序调平台原生 API,在 App 调本地文件系统。你只管调一套代码。 - 自动序列化:不需要手动
JSON.stringify或JSON.parse。你传个对象进去,它存的是对象;你拿出来,它直接就是对象。 - 持久化:数据一旦存入,除非用户手动清理或卸载 App,否则永久存在。
- 跨端屏蔽:在 H5 中它调
二十六、 业务实战与工程化落地 (维护核心系统必看)
这部分内容针对你 JD 中提到的“核心系统维护”、“单元测试”以及“前后端分离开发”等实战场景。
1. Axios 请求与响应拦截器你是怎么做的?
- 请求拦截器 (Request Interceptor):
- 核心工作:在所有请求发出前,自动从
localStorage中读取 Token 并塞进 Header(如:Authorization: Bearer <token>)。 - 大白话:就像是给每封信(请求)都盖上“身份戳”,这样你写业务逻辑时就不用手动传 Token 了。
- 核心工作:在所有请求发出前,自动从
- 响应拦截器 (Response Interceptor):
- 核心工作:统一处理后端返回的状态码。比如拿到
401(未授权)就自动跳转到登录页,拿到500(服务器崩了)就弹出报错信息。 - 大白话:就像是一个“安检员”,把后端回传的东西先审一遍,有问题的直接拦截处理掉,没问题的再发给页面用。
- 核心工作:统一处理后端返回的状态码。比如拿到
2. Vue3 中 ref 和 reactive 怎么选?
ref(全能型):支持所有数据类型(基本类型、对象、数组)。取值需要加个.value(稍显繁琐)。reactive(局限型):只支持引用类型(对象、数组)。- 坑点提醒:
reactive如果直接重新赋值一个新对象,它的响应式会直接“丢失”! - 实战结论:无脑选
ref。代码风格更统一,且能规避很多响应式丢失的 Bug。
3. 说说你对 Composable (组合式函数/自定义 Hook) 的理解?
- 核心逻辑:将跨组件的公用业务逻辑(如:计时器、分页加载、获取地理位置)抽离成独立的
.ts文件。 - 优点:替代了 Vue2 中逻辑混乱的 Mixins。它能清晰地展示数据来源,让逻辑复用像搭积木一样简单且可靠。
4. 为什么会有单元测试?(针对 JD 特设考点)
- 专业回答:它是保障代码质量、支持重构的基石。通过
Vitest或Jest编写自动化测试,可以确保修改一处代码不会引发其他地方的 Bug。 - 大白话:它是你的“代码保险单”。如果你改了一个核心接口逻辑,单元测试能瞬间告诉你全场原本跑通的功能现在还通不通,是中高级开发者的标配技能。
5. Nginx 反向代理解决跨域的原理?
- 原理:浏览器有“同源策略”限制,前端页面不能直接跨域访问后端。但服务器与服务器之间是没有跨域限制的。
- 做法:配置 Nginx 作为一个中转站,前端先调 Nginx(同源),Nginx 再偷偷转发给真正的后端(非同源),最后把结果传回。
- 大白话:Nginx 就是一个“代购”,你买不到的东西(跨域资源),它帮你买回来再亲手交给你。
二十七、 Vue 组件封装与架构思维 (高阶进阶)
这道题主要考查你是否能产出高质量、可维护的代码,也是 JD 中“组件封装”这一项的核心考评。
1. 封装组件的三大核心工具(必说)
- Props (进):定义输入的规格(类型校验、默认值)。
- Emits (出):定义输出的操作(通过自定义事件通知父组件修改数据)。
- Slots (留空):最重要的扩展性工具。通过默认插槽、具名插槽甚至是作用域插槽,预留出不确定的内容位置。
2. 你理解的“高质量组件封装”流程是什么样的?
- 第一阶段:定规格 (Configurable):使用 Props 定义组件外貌。大白话:就像买家具,先量好尺寸,确定它是木质还是金属。
- 第二阶段:立规矩 (One-way Data Flow):严禁在子组件里偷偷改父组件的数据,遵循单向数据流。大白话:如果有变动,通过
emit大声喊(触发事件),让父组件去改。 - 第三阶段:留后门 (Extensibility):预留插槽。大白话:万一以后页面要临时在按钮右边加个小图标,我有插槽就不用重写代码了,直接往坑里塞就行。
3. 面试加分点:如何回答“你封装过什么组件?”
高分回答模版:
“我在上个项目中,针对公司核心业务,封装过一个具备防抖功能的搜索输入组件。
- 它通过
defineModel极简实现了双向绑定。- 我利用
watchEffect自动收集了搜索关键词的变动并进行了** 300ms 的防抖处理**,避免了瞬间发出一堆无效 API 请求。- 我还预留了作用域插槽,让父组件能自定义搜索结果列表的每一行样式,极大提升了团队在多个页面的复用效率。”
4. 封装的真谛:高内聚、低耦合
- 高内聚:组件内部逻辑(如:正则校验、格式化)自己处理好,不麻烦别人。
- 低耦合:组件尽量不依赖外部特定的全局状态(如 Pinia 中的某个死数据),而是通过参数传进去,这样组件在任何地方都能“即插即用”。
二十八、 计算机基础与现代 Web 新特性 (全能选手储备)
这部分内容展现了你的科班素养和技术视野,是进阶大厂的“临门一脚”。
1. 进程 (Process) 和 线程 (Thread) 的区别?(必考基础)
- 进程 (Process):是操作系统分配资源的基础单位。你可以把它想象成一个独立的“工厂”。在现代浏览器里,每个 Tab 标签页通常就是一个独立的渲染进程。
- 线程 (Thread):是进程内部运行的最小单位。你可以把它想象成工厂里的“工人”。一个进程里可以有多个线程(多线程),它们共享进程内部的所有资源。
- 面试重点:
- 浏览器多进程架构:一个页面崩了不影响其他页面,安全性更高。
- JS 是单线程的:因为 JS 涉及 DOM 操作,如果多个人(线程)同时改一个 DOM,浏览器就打架了。所以需要 Event Loop 来调度。
2. 什么是 Vue3 的 Teleport (传送门)?
- 核心逻辑:允许我们将子组件的 HTML 模板,渲染到 DOM 树中当前组件之外的任何地方(通常是
<body>)。 - 实战价值:主要用于弹窗 (Dialog) 或 Tip 提示。能完美避开嵌套层级太深导致的 CSS 布局干扰(如
z-index失效或overflow: hidden切掉内容)。
3. 响应式布局 (Responsive Design) 怎么实现?(布局专项)
- 核心手段:
- 媒体查询 (Media Queries):针对不同屏幕宽度切换不同的 CSS。
- 伸缩布局 (Flex):利用弹性盒子自动适应剩余空间。
- 单位变换 (Rem / Vw):利用 rem 或 vw 单位让元素随屏幕缩放(Rem 配合根字体大小,Vw 配合视口百分比)。
4. 什么是“微前端 (Micro Frontends)”?
- 核心概念:类似于后端的“微服务”。将一个巨大的前端应用拆分成多个可以独立开发、独立部署的子应用,最后整合在一个基座应用里。
- 代表库:
qiankun(基于 single-spa 封装)。 - 应用场景:超大型、跨团队协作的后台管理系统,或者需要在一个页面里集成旧版 Vue2 和新版 Vue3 代码的平滑过渡方案。
💡 全书结语:
面试不仅是考察你的技术“库存”,更是考察你的表达逻辑和解决问题的态度。在回答任何问题时,始终坚持“先给结论、再讲原理、后举实例”的节奏。愿你在这个面试季披荆斩棘,拿下心仪的 Offer!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)