高频前端面试题清单(含参考答案 + 手写题实现)
适合 3~5 年经验前端工程师复习
1. JavaScript 基础
1.1 var / let / const 区别
- var:函数作用域,存在变量提升,可重复声明。
- let:块级作用域,不提升,不可重复声明。
- const:块级作用域,声明后必须初始化,不能重新赋值(对象可修改属性)。
1.2 原型 & 原型链
- 每个函数都有
prototype属性,指向原型对象。 - 每个对象都有
__proto__,指向构造函数的原型。 - 原型链是查找属性和方法的链式结构。
- 面试高频考察点:
instanceof判断、继承实现。
1.3 深拷贝和浅拷贝
- 浅拷贝:只复制第一层对象,引用类型依然共享。
- 深拷贝:
- JSON:
JSON.parse(JSON.stringify(obj)(无法处理函数/undefined/循环引用) - 递归实现:
structuredClone(现代浏览器支持)
1.4 this 的指向
- 默认:指向全局对象(严格模式下为
undefined) - 对象方法:指向当前对象
- 构造函数:指向新创建实例
call / apply / bind:手动指定this- 箭头函数:不绑定
this,从外层作用域继承
1.5 事件循环 & 宏/微任务
- 宏任务:script、setTimeout、setInterval、setImmediate
- 微任务:Promise.then、MutationObserver、queueMicrotask
- 微任务优先级高于宏任务
2. 浏览器原理
2.1 渲染流程
- 解析 HTML -> DOM
- 解析 CSS -> CSSOM
- 合并成 Render Tree
- Layout(计算位置和大小)
- Painting(绘制)
- Compositing(合成显示)
优化重点:减少回流和重绘、合理使用 transform/opacity。
2.2 跨域解决方案
- JSONP
- CORS(设置
Access-Control-Allow-Origin) - Nginx 反向代理
- postMessage、window.name、document.domain
2.3 浏览器缓存
- 强缓存:Expires、Cache-Control
- 协商缓存:Last-Modified + If-Modified-Since、ETag + If-None-Match
3. 性能优化
- 代码分割 + 懒加载(webpack dynamic import)
- Tree-shaking
- 图片优化(压缩、webp)
- 使用 CDN
- 长列表虚拟化(react-window / vue-virtual-scroll-list)
- 避免阻塞渲染的 JS / CSS
4. React / Vue 高频题
React 高频
- 组件通信:props、context、redux、事件回调
- Hooks:useState、useEffect、useMemo、useCallback
- Diff 算法:同层比较、key 的重要性
Vue 高频
- 响应式原理:Object.defineProperty(Vue2)、Proxy(Vue3)
- 生命周期钩子
- 组件通信:props、\(emit、\)attrs、provide/inject、Vuex/Pinia
5. 手写题实现
5.1 防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 测试
const log = debounce(() => console.log("触发"), 300);
log();
log();
5.2 节流函数
function throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
}
};
}
// 测试
const log = throttle(() => console.log("触发"), 500);
setInterval(log, 100);
5.3 Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
let results = [];
let count = 0;
promises.forEach((p, i) => {
Promise.resolve(p)
.then(res => {
results[i] = res;
count++;
if (count === promises.length) resolve(results);
})
.catch(reject);
});
});
}
// 测试
promiseAll([Promise.resolve(1), Promise.resolve(2)])
.then(console.log); // [1,2]
5.4 手写 bind
Function.prototype.myBind = function(context, ...args) {
const fn = this;
return function(...innerArgs) {
return fn.apply(context, args.concat(innerArgs));
};
};
function greet(greeting, name) {
console.log(greeting, name);
}
const sayHi = greet.myBind(null, "Hi");
sayHi("Tom"); // Hi Tom
5.5 深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (hash.has(obj)) return hash.get(obj);
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
for (let key in obj) {
result[key] = deepClone(obj[key], hash);
}
return result;
}
// 测试
const a = { name: "Tom", info: { age: 20 } };
const b = deepClone(a);
b.info.age = 30;
console.log(a.info.age); // 20
5.6 发布订阅模式
class EventBus {
constructor() {
this.events = {};
}
on(event, fn) {
(this.events[event] || (this.events[event] = [])).push(fn);
}
off(event, fn) {
this.events[event] = (this.events[event] || []).filter(f => f !== fn);
}
emit(event, ...args) {
(this.events[event] || []).forEach(fn => fn(...args));
}
}
// 测试
const bus = new EventBus();
function cb(msg) { console.log("收到:", msg); }
bus.on("test", cb);
bus.emit("test", "hello world");
bus.off("test", cb);
6. 算法高频题
6.1 数组去重
const unique = arr => [...new Set(arr)];
console.log(unique([1, 2, 2, 3])); // [1,2,3]
6.2 两数之和
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
if (map.has(target - nums[i])) return [map.get(target - nums[i]), i];
map.set(nums[i], i);
}
}
console.log(twoSum([2,7,11,15], 9)); // [0,1]
6.3 翻转二叉树
function invertTree(root) {
if (!root) return null;
[root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
return root;
}
6.4 LRU 缓存
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map();
}
get(key) {
if (!this.map.has(key)) return -1;
const val = this.map.get(key);
this.map.delete(key);
this.map.set(key, val);
return val;
}
put(key, value) {
if (this.map.has(key)) this.map.delete(key);
this.map.set(key, value);
if (this.map.size > this.capacity) {
this.map.delete(this.map.keys().next().value);
}
}
}
// 测试
const cache = new LRUCache(2);
cache.put(1,1);
cache.put(2,2);
console.log(cache.get(1)); // 1
cache.put(3,3);
console.log(cache.get(2)); // -1 (被淘汰)
6.5 大数相加
function addBigNumber(a, b) {
let i = a.length - 1, j = b.length - 1, carry = 0, res = "";
while (i >= 0 || j >= 0 || carry) {
const sum = (Number(a[i--] || 0) + Number(b[j--] || 0) + carry);
res = (sum % 10) + res;
carry = Math.floor(sum / 10);
}
return res;
}
console.log(addBigNumber("999", "1")); // "1000"
7. HR 面试常问
- 讲一个你最有成就感的项目
- 最近怎么学习前端新技术
- 遇到过最棘手的线上 Bug 怎么排查
- 未来 3 年职业规划
- 场景题:如何设计一个前端灰度发布系统?
- 编码实战:手写一个带缓存的HOC组件(考察闭包+性能)。
- 架构思维:SSR和CSR如何取舍?BFF层如何降低前端负担?
- 建议:把“会用”变成“懂为什么”,并能在业务场景中做出合理的技术