JavaScript 面试常见50题及答案
一、基础概念与数据类型
1. JavaScript 有哪些数据类型?
答案:
基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)
引用类型:Object(包括 Array、Function、Date、RegExp 等)
2. null 和 undefined 的区别?
答案:
undefined:变量已声明但未赋值
null:表示空值,是一个可以赋给变量的特殊值
typeof null 返回 "object",这是历史遗留 bug
3. == 和 === 的区别?
答案:
==:宽松相等,会进行类型转换
===:严格相等,不会进行类型转换
javascript
1 == '1' // true
1 === '1' // false
4. 什么是变量提升?
答案:
var 声明的变量会提升到作用域顶部
只有声明会提升,赋值不会提升
let 和 const 存在暂时性死区,不会提升
5. let、const、var 的区别?
JavaScript 面试常见50题及答案
一、基础概念与数据类型
1. JavaScript 有哪些数据类型?
答案:
基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)
引用类型:Object(包括 Array、Function、Date、RegExp 等)
2. null 和 undefined 的区别?
答案:
undefined:变量已声明但未赋值
null:表示空值,是一个可以赋给变量的特殊值
typeof null 返回 "object",这是历史遗留 bug
3. == 和 === 的区别?
答案:
==:宽松相等,会进行类型转换
===:严格相等,不会进行类型转换
javascript
1 == '1' // true
1 === '1' // false
4. 什么是变量提升?
答案:
var 声明的变量会提升到作用域顶部
只有声明会提升,赋值不会提升
let 和 const 存在暂时性死区,不会提升
5. let、const、var 的区别?

6. 什么是作用域链?
答案:
函数在查找变量时,先从自身作用域查找
找不到则向父级作用域查找,直到全局作用域
这种链式查找关系称为作用域链
7. 什么是闭包?
答案:
函数嵌套函数,内部函数可以访问外部函数的变量
外部函数执行完毕后,其变量仍然被内部函数引用
常见用途:私有变量、函数工厂、模块模式
8. 闭包的优缺点?
答案:
优点:
创建私有变量和方法
实现函数柯里化
模块化开发
缺点:
内存泄漏(如果闭包引用不被释放)
性能考虑(每次创建函数都会创建闭包)
9. 解释词法作用域?
答案:
JavaScript 采用词法作用域(静态作用域)
作用域在函数定义时就确定了,而不是调用时
与动态作用域相对
二、原型与继承
10. 什么是原型链?
答案:
每个对象都有 __proto__ 属性,指向其构造函数的 prototype
prototype 也是对象,也有 __proto__,形成链式结构
查找属性时,沿着原型链向上查找
11. 如何实现继承?
答案:
原型链继承:
javascript
function Parent() {}
function Child() {}
Child.prototype = new Parent()
构造函数继承:
javascript
function Child() {
Parent.call(this)
}
组合继承(最常用):
javascript
function Child() {
Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
Class 继承(ES6):
javascript
class Child extends Parent {
constructor() {
super()
}
}
12. new 操作符做了什么?
答案:
创建一个空对象
将空对象的 __proto__ 指向构造函数的 prototype
将 this 指向这个空对象
执行构造函数
如果构造函数返回对象则返回该对象,否则返回新对象
13. instanceof 的原理?
答案:
检查右边构造函数的 prototype 是否在左边对象的原型链上
实现原理:
javascript
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)
while (true) {
if (proto === null) return false
if (proto === right.prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
三、函数与 this
14. 解释 this 的指向?
答案:
-
普通函数调用:this 指向全局对象(严格模式下为 undefined)
-
方法调用:this 指向调用该方法的对象
-
构造函数调用:this 指向新创建的实例
-
call/apply/bind 调用:this 指向第一个参数
-
箭头函数:this 指向定义时的上下文,不会改变
15. call、apply、bind 的区别?
答案:
javascript
func.call(thisArg, arg1, arg2, ...) // 参数逐个传递
func.apply(thisArg, [argsArray]) // 参数作为数组传递
func.bind(thisArg, arg1, arg2, ...) // 返回新函数,不立即执行
16. 箭头函数与普通函数的区别?
答案:
箭头函数没有自己的 this,继承外层
箭头函数没有 arguments 对象
箭头函数不能作为构造函数(不能用 new)
箭头函数没有 prototype 属性
箭头函数不能使用 yield,不能用作生成器
17. 什么是高阶函数?
答案:
接受函数作为参数
或返回一个函数
例如:map、filter、reduce、bind
异步编程
18. 什么是事件循环?
答案:
JavaScript 是单线程的,通过事件循环处理异步
任务分为宏任务和微任务
执行顺序:同步代码 → 微任务 → 宏任务
宏任务:setTimeout、setInterval、I/O
微任务:Promise.then、process.nextTick、MutationObserver
19. Promise 的状态?
答案:
pending:初始状态
fulfilled:操作成功完成
rejected:操作失败
状态一旦改变就不会再变
20. Promise 的常用方法?
答案:
javascript
Promise.resolve(value) // 返回 resolved 状态的 Promise
Promise.reject(reason) // 返回 rejected 状态的 Promise
Promise.all(iterable) // 所有成功才成功,一个失败就失败
Promise.race(iterable) // 第一个改变状态的 Promise 决定结果
Promise.allSettled(iterable) // 所有 Promise 都完成后返回结果数组
21. async/await 的优点?
答案:
代码更简洁,类似同步写法
更好的错误处理(可以使用 try-catch)
更容易调试
避免回调地狱
22. 实现一个 Promise?
答案:
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
// 简化实现,实际更复杂
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
}
}
四、ES6+ 新特性
23. 解构赋值的用途?
答案:
javascript
// 数组解构
const [a, b] = [1, 2]
// 对象解构
const { name, age } = { name: 'John', age: 30 }
// 函数参数解构
function foo({ x, y }) { return x + y }
// 交换变量
[a, b] = [b, a]
24. 扩展运算符的用途?
答案:
javascript
// 复制数组
const arr2 = [...arr1]
// 合并数组
const arr3 = [...arr1, ...arr2]
// 函数参数
Math.max(...numbers)
// 对象浅拷贝
const obj2 = { ...obj1 }
25. 模板字符串的特性?
答案:
javascript
// 支持换行
const str = `第一行
第二行`
// 支持表达式
const name = 'John'
const greeting = `Hello, ${name}!`
// 标签模板
function tag(strings, ...values) {
// strings: 模板字符串的静态部分
// values: 表达式的值
}
26. Symbol 的作用?
答案:
创建唯一的值,避免属性名冲突
可用作对象的私有属性
内置 Symbol 值如 Symbol.iterator、Symbol.toStringTag
27. Set 和 Map 的区别?
答案:
Set:值的集合,值唯一
Map:键值对的集合,键可以是任意类型
WeakSet:弱引用集合,只能存对象
WeakMap:弱引用键值对,键只能是对象
五、DOM 与 BOM
28. 事件委托是什么?
答案:
将事件监听器绑定到父元素
利用事件冒泡机制处理子元素事件
优点:减少内存消耗,动态元素也能处理
29. 事件冒泡和事件捕获?
答案:
事件冒泡:从目标元素向上传播到根元素
事件捕获:从根元素向下传播到目标元素
DOM 事件流:捕获 → 目标 → 冒泡
addEventListener 第三个参数控制阶段
30. 阻止事件默认行为和冒泡?
答案:
javascript
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡
event.stopImmediatePropagation() // 阻止同一事件的其他监听器
六、性能与安全
31. 什么是防抖和节流?
答案:
防抖:事件触发后延迟执行,如果期间再次触发则重新计时
节流:在一定时间内只执行一次
javascript
// 防抖
function debounce(fn, delay) {
let timer
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// 节流
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
32. 什么是跨域?如何解决?
答案:
浏览器同源策略限制
解决方案:
bash
JSONP(仅 GET 请求)
CORS(服务器设置响应头)
代理服务器
postMessage
WebSocket
33. 什么是 XSS 攻击?如何防范?
答案:
跨站脚本攻击,注入恶意脚本
防范措施:
输入过滤和转义
设置 HttpOnly Cookie
使用 CSP(内容安全策略)
避免内联事件处理
34. 什么是 CSRF 攻击?如何防范?
答案:
跨站请求伪造,诱导用户发送恶意请求
防范措施:
使用 CSRF Token
验证 Referer 头
设置 SameSite Cookie
验证码
七、算法与数据结构
35. 数组去重的方法?
答案:
javascript
// 1. Set
[...new Set(array)]
// 2. filter + indexOf
array.filter((item, index) => array.indexOf(item) === index)
// 3. reduce
array.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])
36. 数组扁平化的方法?
答案:
javascript
// 1. flat
arr.flat(Infinity)
// 2. reduce + 递归
function flatten(arr) {
return arr.reduce((acc, cur) =>
Array.isArray(cur)
? acc.concat(flatten(cur))
: acc.concat(cur),
[])
}
// 3. toString(仅限数字数组)
arr.toString().split(',').map(Number)
37. 深拷贝的实现?
答案:
javascript
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (map.has(obj)) return map.get(obj)
const clone = Array.isArray(obj) ? [] : {}
map.set(obj, clone)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map)
}
}
return clone
}
八、 浏览器相关
38. 从输入 URL 到页面显示的过程?
答案:
DNS 解析
TCP 连接
发送 HTTP 请求
服务器处理请求并返回响应
浏览器解析渲染
连接结束
39. 重排和重绘?
答案:
重排:布局改变,需要重新计算
重绘:外观改变,不需要重新布局
优化:避免频繁操作 DOM,使用 transform 和 opacity
40. 内存泄漏的原因?
答案:
意外的全局变量
未清理的定时器
闭包滥用
未解绑的事件监听
DOM 引用未清除
九、模块化
41. CommonJS 和 ES6 Module 的区别?

42. AMD 和 CMD 的区别?
答案:
AMD:异步加载,提前执行(RequireJS)
CMD:异步加载,延迟执行(SeaJS)
现在多用 ES6 Module
其他重要概念
43. 什么是柯里化?
答案:
将多参数函数转化为单参数函数序列
javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
44. 什么是函数组合?
答案:
将多个函数组合成一个新函数
javascript
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x)
}
}
45. 什么是尾调用优化?
答案:
函数最后一步调用另一个函数
ES6 严格模式下支持
可以优化递归性能
46. Generator 函数的作用?
答案:
可以暂停执行和恢复执行
配合 yield 使用
常用于异步编程(async/await 的基础)
javascript
function* gen() {
yield 1
yield 2
return 3
}
47. Proxy 和 Reflect?
答案:
Proxy:代理对象,拦截操作
Reflect:操作对象的 API,与 Proxy 对应
javascript
const proxy = new Proxy(target, {
get(target, prop) {
return Reflect.get(target, prop)
}
})
48. WeakMap 和 WeakSet 的特点?
答案:
弱引用,不影响垃圾回收
键/值必须是对象
不可遍历
没有 size 属性
49. 什么是 Optional Chaining?
答案:
ES2020 新增,可选链操作符 ?.
避免访问嵌套对象时的空值错误
bash
const name = obj?.user?.name
50. 什么是 Nullish Coalescing?
答案:
ES2020 新增,空值合并操作符 ??
仅在左侧为 null 或 undefined 时返回右侧
javascript
const value = a ?? 'default'