JavaScript 面试题
JavaScript 面试测试你对语言核心机制(作用域、闭包、异步行为、原型链)的掌握——通常包含简短的编码任务。
JavaScript 面试涵盖内容
作用域与闭包
词法作用域、闭包、提升和暂时性死区。
异步与事件循环
回调、Promise、async/await、微任务 vs 宏任务。
this 与原型
this 的绑定方式、call/apply/bind 以及原型继承。
类型与强制转换
== 与 === 的区别、真假值以及常见的强制转换陷阱。
JavaScript 面试题示例
- 什么是闭包?给出一个实际用途。好回答应覆盖
- 闭包定义:函数与词法环境的组合
- 作用域链保留外部变量
- 私有变量封装
- 实际用途:模块模式,函数工厂
查看范例答案
闭包是指一个函数和其词法环境的组合,该环境包含了函数创建时所有可访问的变量。即使外部函数已经返回,内部函数仍然可以访问外部函数的变量。闭包的核心机制是作用域链,内部函数保存对外部函数作用域的引用。实际用途包括:创建私有变量,例如通过闭包实现计数器,外部无法直接修改计数器值;在事件监听中保存状态;使用模块模式隐藏实现细节。常见的陷阱是内存泄漏,因为闭包会保持对外部变量的引用,如果闭包长期存在需要注意及时清理。
- 解释事件循环、微任务和宏任务。好回答应覆盖
- 事件循环:单线程异步模型
- 宏任务:setTimeout, setInterval, I/O
- 微任务:Promise.then, MutationObserver
- 执行顺序:同步代码→微任务→宏任务
- 常见例子:Promise.then早于setTimeout
查看范例答案
事件循环是JavaScript实现异步执行的机制。单线程环境下,同步代码先执行,遇到异步任务则放入任务队列。宏任务包括setTimeout、setInterval、I/O等,微任务包括Promise的回调、MutationObserver等。事件循环的每次迭代先执行一个宏任务,然后清空所有微任务队列。例如,setTimeout和Promise.then同时注册时,Promise.then会先执行,因为它属于微任务。理解这一机制对避免异步执行顺序错误至关重要,尤其是在Node.js和浏览器环境中。常见面试追问包括:async/await的微任务本质,以及process.nextTick在Node.js中的优先级。
- == 和 === 有什么区别?什么时候可以接受 ==?好回答应覆盖
- == 允许类型转换,=== 严格比较
- 类型转换规则:ToNumber, ToPrimitive
- == 的陷阱:0==''为true, null==undefined
- 可接受 == 场景:检查null/undefined
- 最佳实践:优先使用===
查看范例答案
== 是宽松相等运算符,比较时会进行类型转换;=== 是严格相等运算符,不进行类型转换。== 遵循一套复杂的规则,例如字符串与数字比较时字符串转为数字,对象与原始值比较时对象转为原始值。常见陷阱包括:0 == '' 为true,null == undefined 为true,而null === undefined 为false。== 在特定场景下可以被接受:当你明确知道类型且需要宽松比较时,例如检查一个值是null或undefined可以写为 value == null,它同时覆盖了null和undefined。但更推荐始终使用===,以避免意料之外的转换。在团队中应统一规范,通常只允许在比较null/undefined时使用==。
- 如何确定 `this`?call/apply/bind 如何改变它?好回答应覆盖
- 默认绑定:非严格模式下全局对象,严格模式undefined
- 隐式绑定:方法调用对象
- 显式绑定:call, apply, bind
- new绑定:this指向新实例
- 箭头函数:词法this,不绑定自身this
查看范例答案
this的值取决于函数调用方式。默认绑定:独立函数调用时,非严格模式下this指向全局对象(如window),严格模式下为undefined。隐式绑定:作为对象方法调用时,this指向该对象。隐式绑定可能丢失,例如将方法赋值给变量后调用。显式绑定可以通过call、apply、bind强制指定this,call和apply立即调用,bind返回新函数。new绑定:使用new调用构造函数时,this指向新创建的对象。箭头函数不绑定自己的this,继承外层词法作用域的this。理解这些规则能帮助避免常见的this指向错误,尤其是在回调函数和事件处理中。call和apply区别仅在于传参方式,apply使用数组。
- 实现防抖(或节流)函数。好回答应覆盖
- 防抖:延迟执行,调用取消前一次定时器
- 支持leading和trailing选项
- 时间复杂度: O(1) 每次调用
- 空间复杂度: O(1) 仅存储定时器ID
- 应用场景:搜索框输入,窗口resize
查看范例答案
防抖函数用于限制频繁触发的事件,只在最后一次触发后等待指定延迟后执行回调。其原理是每次触发时清除前一次的定时器并重新设置,从而保证只有停止触发后才执行。实现时可以通过leading参数控制是否在首次触发时立即执行。防抖的时间复杂度为O(1),每次调用只进行清除和设置定时器的操作;空间复杂度为O(1),仅存储定时器ID和函数。常见应用场景包括搜索框输入建议、窗口resize事件处理。注意:如果事件触发频率很高且延迟较长,回调可能永远不会执行,需要合理设置延迟时间。
参考代码javascript function debounce(func, wait, immediate = false) { let timeout = null; return function(...args) { const context = this; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; if (!immediate) func.apply(context, args); }, wait); if (callNow) func.apply(context, args); }; } - var、let 和 const 有什么区别?好回答应覆盖
- var: 函数作用域,变量提升,可重复声明
- let: 块级作用域,暂时性死区,不能重复声明
- const: 块级作用域,必须初始化,不能重新赋值
- 全局声明:var挂载到window,let/const不挂载
- 推荐:默认使用const,需要重新赋值用let
查看范例答案
var声明的变量具有函数作用域,存在变量提升(声明提升到作用域顶部,但赋值不提升),可重复声明,在全局作用域下会挂载到window对象。let和const是ES6引入的块级作用域变量,不存在变量提升,存在暂时性死区(在声明前访问会报错),不能重复声明。const必须在声明时初始化,且不能重新赋值(但对于对象类型,属性可以修改)。let用于需要重新赋值的变量。在全局作用域,let和const不会创建window对象上的属性。最佳实践是:默认使用const,只有在确实需要重新赋值时才使用let,避免使用var。常见陷阱是var在循环中导致闭包问题,而let可以解决。
如何准备
- 能够预测棘手代码片段的输出(循环中的闭包、异步顺序)。
- 练习从头实现防抖、节流和简单的 Promise。
- 充分理解事件循环,能够准确解释异步顺序。
- 了解现代特性(let/const、箭头函数、解构、模块)及其存在的原因。
常见问题
JavaScript 仍然与 React 分开问吗?
是的——许多面试包含纯 JavaScript 轮次,涉及闭包、异步和 `this`,与任何框架无关。
需要了解 TypeScript 吗?
前端岗位越来越需要,但核心 JavaScript 机制仍然是面试官测试的基础。
哪些 JavaScript 主题最常见?
闭包、事件循环和 Promise、`this` 绑定,以及实现防抖或节流等工具函数。
如何练习 JavaScript 面试?
在时间压力下预测代码片段输出并实现小型工具函数。Offersly 可以根据你的简历生成 JavaScript 题目。
练习 JavaScript 题目,即时获取 AI 反馈
上传简历,获得个性化模拟面试,并了解需要改进的地方——免费开始。