2026前端高频面试题总结(Vue/JS/网络/Webpack/性能优化/手写)
最核心的"八股文"+最高频的手写题,一网打尽,祝你面试顺利。
你好,我是元宝。今天,我为你精心整理了一份覆盖Vue、JavaScript、网络、Webpack、性能优化等核心领域的前端高频面试题与手写题大全。
本文不仅梳理了最常被问到的原理和概念(俗称"八股"),还附带了思路清晰、可直接运行的手写代码实现。内容全面,语言通俗,旨在帮助你高效复习,攻克面试。
一、引言
前端面试中,"八股文"是检验基础功底的核心,手写题则是考察实战能力的关键。无论是校招还是社招,熟练掌握这些知识点都至关重要。
本文汇总了面试中最常考的经典八股知识点和高频手写题,涵盖 JS 基础、Vue框架原理、工程化、浏览器机制、网络协议等核心领域。每道题都附带清晰解答和思路解析,你可以直接用于复习,也可以动手敲代码加深理解。
二、JavaScript核心基础
1. 原型与原型链
这是JS面向对象的基石。
- 原型 :每个函数(构造函数)都有一个
prototype属性,它指向一个对象,这个对象就是该函数的"原型对象"。这个对象包含了可以由该构造函数创建的所有实例共享的属性和方法。 - 原型链 :每个对象都有一个
__proto__属性(现已更推荐使用Object.getPrototypeOf()),指向它的构造函数的原型对象。当访问一个对象的属性时,如果对象自身没有,就会沿着__proto__这条链向上查找,直到找到或到达终点(Object.prototype.__proto__为null)。这条链就是原型链,是实现继承的关键。
简单来说:原型是共享方法的"公共区域",原型链是查找属性的"链式路径"。
2. new操作符执行流程
当你使用 new Constructor() 时,背后发生了什么?
- 创建空对象:在内存中创建一个全新的空对象。
- 链接原型 :将这个新对象的
__proto__属性,指向构造函数的prototype属性,从而继承原型上的方法。 - 绑定this并执行 :将构造函数内部的
this绑定到这个新对象上,然后执行构造函数内部的代码(通常是为新对象添加属性)。 - 返回新对象:如果构造函数没有显式返回一个对象,那么就返回这个新创建的对象。如果构造函数返回了一个对象(非原始值),则返回这个对象。
3. 浅拷贝 vs 深拷贝
这是一个高频考点,核心在于理解基本类型 和引用类型在内存中的存储差异。
-
浅拷贝 :只复制对象的第一层属性。如果属性值是引用类型(对象、数组),拷贝的其实是内存地址 。修改拷贝后的对象会影响原对象。
javascript// 浅拷贝方法举例 let obj1 = { a: 1, b: { c: 2 } }; let obj2 = Object.assign({}, obj1); let obj3 = { ...obj1 }; obj2.b.c = 999; console.log(obj1.b.c); // 999,原对象被影响了! -
深拷贝 :完全复制一个对象,包括其内部的嵌套对象。新旧对象互不影响。
javascript// 简单深拷贝(有局限性) let newObj = JSON.parse(JSON.stringify(oldObj)); // 局限性:无法拷贝函数、undefined、循环引用等要实现一个健壮的深拷贝函数,需要递归遍历对象,并用
WeakMap处理循环引用。后面"手写题"部分有详细实现。
4. 闭包
闭包是JavaScript中一个强大且重要的概念。
- 定义 :当一个内部函数 引用了其外部函数的变量或参数,并且这个内部函数在外部函数之外被访问时,就形成了闭包。闭包使得外部函数的作用域在函数执行完毕后也不会被销毁。
- 用途 :
- 创建私有变量:模拟面向对象中的私有属性。
- 延长变量生命周期:例如防抖/节流函数中的定时器变量。
- 缺点 :如果使用不当,闭包中引用的变量无法被垃圾回收,可能导致内存泄漏。
5. Promise、Async/Await
这是处理异步操作的现代方案。
-
Promise :一个表示异步操作最终完成或失败的对象。它有三种状态:
pending(进行中)、fulfilled(已成功)、rejected(已失败)。状态一旦改变,就不可逆。通过.then()和.catch()处理成功和失败。 -
Async/Await :基于Promise的语法糖,让你能用同步的方式 写异步代码,代码更清晰。
javascriptasync function fetchData() { try { const data = await someAsyncFunction(); // 等待Promise解决 console.log(data); } catch (error) { console.error(error); } }
6. 防抖与节流
这两个是优化高频触发操作(如滚动、输入、点击)的利器。
- 防抖 :事件被触发后,等待一段时间再执行函数。如果在这段时间内事件又被触发,则重新计时 。核心是"只执行最后一次"。适用于搜索框输入联想。
- 节流 :事件被持续触发时,保证在固定的时间间隔内只执行一次 函数。核心是"有节制地执行"。适用于滚动加载、窗口调整。
手写实现代码见后文"手写题"部分。
三、Vue全家桶高频面试题
1. MVVM模式
Vue的核心思想。它将应用程序分为三层:
- Model(模型) :数据层,如
data、vuex状态、从API获取的数据。 - View(视图) :UI层,即模板(
template),负责展示数据。 - ViewModel(视图模型) :Vue实例,是连接View和Model的桥梁。它通过数据绑定 (
v-model)将Model的变化自动反映到View,通过DOM监听 (v-on)将View的交互事件自动更新Model。
2. 计算属性 vs 侦听器
- 计算属性 :
computed- 有缓存:基于它们的响应式依赖进行缓存。依赖的数据不变,就不会重新计算。
- 同步:适合做模板中复杂表达式的计算和数据格式化。
- 侦听器 :
watch- 无缓存:数据变化就会执行。
- 支持异步 :适合在数据变化时执行异步操作或开销较大的操作(如Ajax请求)。
3. 生命周期钩子
Vue实例从创建到销毁的全过程,有8个核心钩子:
- beforeCreate :实例初始化,
data和methods都还未初始化。 - created :实例创建完成,
data和methods已可用。常用于发起异步数据请求。 - beforeMount:模板编译完成,但尚未挂载到DOM上。
- mounted :实例已挂载到DOM。可以安全地操作DOM和访问
$refs。 - beforeUpdate:数据更新了,但DOM还未重新渲染。
- updated :DOM已完成更新。避免在此钩子中修改数据,可能导致无限循环。
- beforeDestroy :实例销毁前,此时实例完全可用。适合做清除定时器、解绑事件等收尾工作。
- destroyed:实例销毁后,所有的事件监听器和子实例都已被销毁。
4. Vue 2 响应式原理
Vue 2 通过 Object.defineProperty() 来实现数据劫持和监听。
- 过程 :遍历
data中的所有属性,用Object.defineProperty()为每个属性定义getter和setter。当读取属性时触发getter进行依赖收集 ,当修改属性时触发setter通知更新。 - 局限性 :
- 无法检测对象属性的添加或删除 (需要用
Vue.set/this.$set)。 - 无法检测数组索引的直接设置 和长度的修改 。Vue重写了数组的7个方法(
push,pop,shift,unshift,splice,sort,reverse)来触发更新。
- 无法检测对象属性的添加或删除 (需要用
5. Vue 3 响应式原理
Vue 3 使用 Proxy 替代了Object.defineProperty。
- 优势 :
- 功能更强大:可以监听对象和数组的所有操作(增、删、改、查)。
- 性能更好:惰性代理,只在访问时递归,对嵌套对象的访问更高效。
- 缺点:不兼容IE浏览器。
6. 组件通信
- 父子通信 :
- 父传子 :
props - 子传父 :
$emit
- 父传子 :
- 跨级/兄弟通信 :
- Event Bus:创建一个中央事件总线(Vue实例)来管理事件。适合小型项目。
- Vuex / Pinia:全局状态管理,是复杂应用的最佳实践。
- 其他 :
- provide / inject :祖先组件通过
provide提供数据,后代组件通过inject注入。适用于多层嵌套的组件。
- provide / inject :祖先组件通过
四、网络与浏览器原理
1. 从输入URL到页面显示
这是一个经典的综合性问题,考察知识广度。
- URL解析:浏览器解析地址。
- DNS解析 :将域名(如
www.baidu.com)解析为IP地址。 - 建立TCP连接:与服务器通过"三次握手"建立TCP连接。
- 发送HTTP请求:浏览器发送HTTP请求报文(包含请求行、请求头、请求体)。
- 服务器处理并返回响应:服务器处理请求,返回HTTP响应报文。
- 浏览器解析渲染 :
- 解析HTML构建DOM树。
- 解析CSS构建CSSOM树。
- 将DOM和CSSOM合并成渲染树。
- 布局:计算每个节点的位置和大小。
- 绘制:将布局后的节点绘制到屏幕上。
- 连接关闭:通过"四次挥手"断开TCP连接。
2. 跨域解决方案
浏览器出于安全考虑,有"同源策略",禁止一个源的网页与另一个源的资源交互。
- CORS :最标准的解决方案。服务器在响应头中设置
Access-Control-Allow-Origin允许特定或所有源访问。 - JSONP :利用
<script>标签没有跨域限制的特性。只能用于GET请求。 - 代理服务器:在开发环境中,配置开发服务器(如webpack-dev-server)将API请求代理到目标服务器,绕过浏览器的同源策略。
3. HTTP缓存机制
核心是强缓存 和协商缓存。
- 强缓存 :浏览器不向服务器发起请求,直接使用本地缓存。由
Cache-Control和Expires响应头控制。Cache-Control: max-age=3600表示资源在1小时内都有效。
- 协商缓存 :浏览器向服务器发起请求,由服务器判断资源是否过期。由
ETag/If-None-Match和Last-Modified/If-Modified-Since这两对请求/响应头控制。- 如果资源未变,服务器返回
304 Not Modified,浏览器使用本地缓存。 - 如果资源已变,服务器返回
200 OK和新资源。
- 如果资源未变,服务器返回
五、性能优化
1. 代码层面
- 懒加载 :图片懒加载、路由懒加载(Vue的
component: () => import('./MyComponent.vue'))。 - 代码分割 :利用Webpack等工具将代码拆分成多个
chunk,按需加载。 - Tree Shaking:移除JavaScript上下文中未引用的代码(dead-code)。
- 防抖与节流:控制高频事件(输入、滚动)的回调频率。
2. 资源层面
- 图片优化:压缩图片、使用WebP等新格式、使用雪碧图(CSS Sprites)。
- 使用CDN:将静态资源分发到离用户更近的节点,加速访问。
- 开启Gzip压缩:服务器对传输的资源进行压缩,减少体积。
3. 渲染层面
- 减少重绘和回流 :
- 重绘 :元素样式改变但不影响布局(如
color)。 - 回流 :元素的尺寸、结构、位置等发生改变,导致浏览器重新计算布局。回流的开销比重绘大得多。
- 优化 :避免频繁操作DOM样式,使用
transform和opacity来实现动画(它们能触发GPU加速,创建独立的合成层)。
- 重绘 :元素样式改变但不影响布局(如
- 骨架屏:在首屏内容加载前,先展示一个页面结构轮廓,提升用户体验。
六、高频手写题(思路+代码)
理论懂了,代码也要会写。以下是最高频的几个手写题。
1. 手写深拷贝(支持循环引用)
javascript
function deepClone(target, map = new WeakMap()) {
// 处理基本类型和 null/undefined
if (target === null || typeof target !== 'object') {
return target;
}
// 处理循环引用
if (map.has(target)) {
return map.get(target);
}
let cloneTarget;
// 处理数组
if (Array.isArray(target)) {
cloneTarget = [];
} else {
cloneTarget = {};
}
// 缓存已拷贝的对象
map.set(target, cloneTarget);
// 递归拷贝属性
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key], map);
}
}
return cloneTarget;
}
// 测试
const obj = { a: 1, b: { c: 2 } };
const clonedObj = deepClone(obj);
clonedObj.b.c = 3;
console.log(obj.b.c); // 2,原对象未被修改
2. 手写防抖函数
javascript
function debounce(fn, delay) {
let timer = null;
return function (...args) {
const context = this;
// 每次触发都清除之前的定时器
clearTimeout(timer);
// 设置新的定时器
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
// 测试
window.addEventListener('resize', debounce(() => {
console.log('窗口大小改变了');
}, 200));
3. 手写节流函数
javascript
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
const context = this;
// 如果距离上次执行的时间超过了间隔,就执行函数
if (now - lastTime >= delay) {
fn.apply(context, args);
lastTime = now; // 更新上次执行时间
}
};
}
// 测试
window.addEventListener('scroll', throttle(() => {
console.log('滚动中...');
}, 100));
4. 手写Promise(简易版,支持then链式调用)
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
}
七、总结
前端面试的"八股文"是基础,手写题是能力的体现。复习时切忌死记硬背,理解其背后的原理 和设计思想才是关键。
建议的学习路径:
- 理解概念:把每个知识点(如原型链、闭包、响应式)的核心逻辑用自己的话讲清楚。
- 动手实践:对于手写题,一定要在编辑器里亲自敲一遍,调试运行,理解每一行代码的作用。
- 关联思考:将知识点与你做过的项目、遇到的实际问题联系起来,思考如何应用。
希望这份精心整理的2026前端高频面试题总结,能成为你求职路上的得力助手。祝你面试顺利,拿下心仪的Offer!