2025 前端面试必考的 7 个 JavaScript 实战问题

130 阅读3分钟

如果你还在背“什么是闭包”这种基础问题,你可能已经落伍了。2025 年的前端面试,更看重你在真实业务场景中解决复杂问题的能力。这篇文章将带你梳理 7 个高频且实用的 JavaScript 面试问题,并结合最佳实践和踩坑经验,帮你直接对标实战。

1. Event Loop 如何处理 Promise、setTimeout 和 async/await

事件循环是 JS 运行时的核心机制,理解微任务(Microtask)和宏任务(Macrotask)的执行顺序是解决异步问题的基础。

console.log("A");

setTimeout(() => {
  console.log("B");
}, 0);

Promise.resolve().then(() => {
  console.log("C");
});

console.log("D");
// 输出顺序:A D C B

原因在于:

  • Promise.then 属于微任务,会在本轮宏任务结束前执行。
  • setTimeout 属于宏任务,要等当前任务队列清空后执行。
  • async/await 的 await 也会产生微任务。

建议:调试复杂异步时,使用 console.log 标记执行顺序,或用浏览器 Performance 面板分析任务流。

2. 浅拷贝 vs 深拷贝在状态管理中的坑

const original = { user: { name: "Arnold", age: 19 } };
const shallowCopy = { ...original };
shallowCopy.user.age = 31;
console.log(original.user.age); // 31

浅拷贝仅复制第一层引用,嵌套对象仍指向同一内存地址。React、Vue 中错误使用浅拷贝,会造成状态被意外修改但 UI 不更新的隐性 bug。

深拷贝方案:

const deepCopy = structuredClone(original);
// 或 JSON 序列化(有类型丢失风险)
const deepCopy2 = JSON.parse(JSON.stringify(original));

建议:对复杂对象状态更新时,结合不可变数据(Immutable Data)思想,避免状态被共享引用污染。

3. WeakMap / WeakSet 在防止内存泄漏中的作用

let obj = {};
const wm = new WeakMap();
wm.set(obj, "cache data");
obj = null; // 键对象无引用后可被 GC

WeakMap/WeakSet 的键是弱引用,不会阻止垃圾回收,非常适合存储与 DOM 节点、缓存数据等短生命周期绑定的内容,防止 SPA 长时间运行导致内存泄漏。

4. 事件委托(Event Delegation)应对动态 UI

document.getElementById("list").addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log("Clicked:", e.target.textContent);
  }
});

原理是事件冒泡。相比直接绑定到每个子节点,委托到父元素更节省内存,并支持后续动态插入的节点。React 事件系统底层也大量应用了这一思想。

5. Generator 函数的实用场景

function* idGenerator() {
  let id = 1;
  while (true) yield id++;
}
const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

Generator 可以中途暂停执行,适合处理分页数据流、按需加载、任务队列等场景。虽然业务中使用频率不高,但在性能优化中非常有价值。

6. 异步代码中的竞态条件调试与防御

let latestCall = 0;
async function handleSearch(query) {
  latestCall++;
  const id = latestCall;
  const result = await fetchData(query);
  if (id === latestCall) console.log(result);
}

核心是用唯一标识(或 AbortController)确保只处理最后一次请求,避免搜索、表单提交中出现脏数据覆盖的问题。

7. bind / call / apply 的区别与坑

function greet() { console.log(this.name); }
const person = { name: "Arnold" };
greet.call(person);
greet.apply(person);
greet.bind(person)();
  • call:立即执行,参数逐个传入。
  • apply:立即执行,参数以数组传入。
  • bind:返回绑定 this 的新函数。

常见坑:将对象方法直接传递给回调(如 setTimeout)会丢失 this,需要提前 bind

总结

现代面试更关注你是否能定位复杂异步问题、避免状态污染、防止内存泄漏,以及写出稳定可维护的代码。掌握这些实战技巧,比死记硬背基础概念更能让你脱颖而出。

延伸阅读