前端 JavaScript 面试题汇总及深度解析(2025版)
一、JavaScript 基础核心
1. 解释 JavaScript 中的执行上下文(Execution Context)
问题:请描述 JavaScript 执行上下文的创建过程及其核心组成
答案: 执行上下文是 JavaScript 代码执行时的环境抽象概念,包含三个核心部分:
- 变量环境(Variable Environment)
- 存储 var 声明的变量和函数声明
- 在编译阶段完成初始化(变量提升)
- 词法环境(Lexical Environment)
- 存储 let/const 声明的变量
- 具有块级作用域特性
- this 绑定
- 在运行时确定指向
解析:
function demo() {
console.log(a); // undefined
var a = 10;
let b = 20;
}
- 编译阶段:创建变量环境(a=undefined),初始化函数声明
- 执行阶段:执行赋值操作,词法环境中的 b 在声明前访问会报错(TDZ)
2. 闭包与内存泄漏
问题:如何理解闭包导致的内存泄漏?如何检测和避免?
答案:
闭包内存泄漏的典型场景:
function createClosure() {
const largeData = new Array(1000000).fill('*');
return function() {
console.log('Closure created');
};
}
let fn = createClosure();
即使不再使用fn,largeData仍被闭包引用无法回收
解决方案:
- 使用 Chrome DevTools 的 Memory 面板进行堆快照对比
- 主动解除引用:
fn = null - 使用 WeakMap 代替常规对象存储大数据
3. 原型链继承的多种实现方式
问题:实现一个继承方案,要求:
- 子类继承父类属性
- 子类可扩展方法
- 避免引用类型共享问题
答案及解析:
// 最佳实践:寄生组合继承
function Parent(name) {
this.name = name;
this.colors = ['red'];
}
Parent.prototype.say = function() {};
function Child(name) {
Parent.call(this, name); // 继承实例属性
}
// 原型继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// 测试
const c1 = new Child('Tom');
c1.colors.push('blue');
const c2 = new Child('Jerry');
console.log(c2.colors); // ['red']
二、异步编程与事件循环
4. 宏任务与微任务执行顺序
问题:分析以下代码执行顺序
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
console.log('script end');
答案:
- script start
- script end
- promise1
- promise2
- setTimeout
解析:
- 主线程任务(宏任务)先执行
- 微任务队列会在每个宏任务结束后立即清空
- 流程示意图:
宏任务队列: [主脚本]
微任务队列: []
执行主脚本 →
宏任务队列: []
微任务队列: [promise1回调]
执行微任务 →
宏任务队列: [setTimeout回调]
5. async/await 实现原理
问题:async 函数在低版本浏览器如何兼容?
答案:
通过 Babel 转换后的代码:
async function demo() {
await fetchData();
}
// 转换为:
function demo() {
return _asyncToGenerator(function* () {
yield fetchData();
})();
}
function _asyncToGenerator(fn) {
return function() {
const gen = fn.apply(this, arguments);
return new Promise((resolve, reject) => {
function step(key, arg) {
try {
const info = gen[key](arg);
const value = info.value;
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(
v => step("next", v),
e => step("throw", e)
);
}
} catch (e) {
reject(e);
}
}
return step("next");
});
};
}
三、ES6+ 新特性深度解析
6. Proxy 与响应式实现
问题:使用 Proxy 实现简易 Vue 数据响应
答案:
const handler = {
get(target, key) {
track(target, key); // 依赖收集
return Reflect.get(...arguments);
},
set(target, key, value) {
const result = Reflect.set(...arguments);
trigger(target, key); // 触发更新
return result;
}
};
function reactive(obj) {
return new Proxy(obj, handler);
}
// 使用示例
const state = reactive({ count: 0 });
effect(() => {
console.log('Count changed:', state.count);
});
state.count++; // 触发日志输出
四、浏览器原理与性能优化
7. Event Loop 渲染时机
问题:requestAnimationFrame 与 requestIdleCallback 的区别
解析:
| 特性 | requestAnimationFrame | requestIdleCallback |
|---|---|---|
| 触发时机 | 每次重绘前 | 主线程空闲时 |
| 执行频率 | 60fps(约16.6ms) | 不确定,可能被高优先级阻断 |
| 适用场景 | 动画更新 | 非关键后台任务 |
| 是否可能丢帧 | 否 | 是 |
五、高频手写代码题
8. 实现 Promise.all
要求:
- 处理可迭代对象
- 保持结果顺序
- 快速失败机制
Promise.myAll = function(iterable) {
const tasks = Array.from(iterable);
if (tasks.length === 0) return Promise.resolve([]);
return new Promise((resolve, reject) => {
const results = new Array(tasks.length);
let count = 0;
tasks.forEach((task, index) => {
Promise.resolve(task).then(res => {
results[index] = res;
if (++count === tasks.length) {
resolve(results);
}
}).catch(reject);
});
});
};
六、TypeScript 高级类型
9. 条件类型与 infer 关键字
问题:实现提取 Promise 泛型类型
type UnpackPromise<T> = T extends Promise<infer R> ? R : never;
// 测试
type Test = UnpackPromise<Promise<string>>; // string
七、设计模式实战
10. 观察者模式 vs 发布订阅模式
区别:
- 观察者:Subject 直接维护 Observer 列表
- 发布订阅:通过中间通道(Event Channel)解耦
发布订阅实现:
class EventBus {
constructor() {
this.events = new Map();
}
on(type, handler) {
const handlers = this.events.get(type) || [];
handlers.push(handler);
this.events.set(type, handlers);
}
emit(type, ...args) {
const handlers = this.events.get(type) || [];
handlers.forEach(fn => fn(...args));
}
off(type, handler) {
const handlers = this.events.get(type) || [];
this.events.set(
type,
handlers.filter(fn => fn !== handler)
);
}
}