50道JavaScript高频面试题及答案总结
基础概念与语法
1. JavaScript的数据类型有哪些?
答案:
- 原始类型(Primitive Types):
String、Number、Boolean、Null、Undefined、Symbol(ES6)、BigInt(ES2020)
- 引用类型(Reference Types):
Object(包括Array、Function、Date、RegExp等)
typeof null // "object" (历史遗留问题)
typeof [] // "object"
typeof function() {} // "function"
2. let、const和var的区别?
答案:
| 特性 | var | let | const |
|------|-----|-----|-------|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 是(初始化为undefined) | 是(TDZ暂时性死区) | 是(TDZ暂时性死区) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 初始值 | 可选 | 可选 | 必须 |
| 可重新赋值 | 是 | 是 | 否(对于原始类型) |
3. 什么是闭包?有什么作用?
答案:
闭包是指函数能够访问并记住其词法作用域,即使函数在其词法作用域之外执行。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
作用:
创建私有变量和方法
实现函数工厂和柯里化
模块模式
事件处理和回调
4. 原型和原型链是什么?
答案:
-
原型(prototype):每个函数都有一个prototype属性,指向一个对象
-
原型链:访问对象属性时,如果对象本身没有,会通过__proto__向上查找,形成链式结构
-
构造函数、原型、实例的关系:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, ${this.name}`);
};
const person = new Person('John');
// person.__proto__ === Person.prototype
// Person.prototype.__proto__ === Object.prototype
// Object.prototype.__proto__ === null
5. call、apply、bind的区别?
答案:
func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
func.bind(thisArg, arg1, arg2, ...)
区别:
call:立即执行,参数逐个传递
apply:立即执行,参数数组传递
bind:返回新函数,不立即执行,可预设参数
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // Hello, Alice!
greet.apply(person, ['Hi', '!!']); // Hi, Alice!!
const boundGreet = greet.bind(person, 'Hey');
boundGreet('!!!'); // Hey, Alice!!!
6. 事件循环(Event Loop)机制?
答案:
JavaScript是单线程的,事件循环是其异步编程的核心。
执行顺序:
执行同步任务
执行微任务(Microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver
执行宏任务(Macrotask):setTimeout、setInterval、setImmediate、I/O、UI渲染
重复上述过程
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步
// 输出:1 4 3 2
7. 什么是深拷贝和浅拷贝?如何实现?
答案:
- 浅拷贝:只复制第一层属性,嵌套对象共享引用
- 深拷贝:完全复制,新对象与原对象完全独立
实现方式:
// 浅拷贝
const shallowCopy1 = Object.assign({}, obj);
const shallowCopy2 = {...obj};
const shallowCopy3 = [];
arr.forEach(item => shallowCopy3.push(item));
// 深拷贝
const deepCopy1 = JSON.parse(JSON.stringify(obj)); // 缺点:不能处理函数、undefined、循环引用
const deepCopy2 = structuredClone(obj); // 现代API(Node 17+,浏览器支持)
手动实现深拷贝:
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;
}
8. Promise是什么?有哪些状态?
答案:
Promise是异步编程的解决方案,有三种状态:
pending:初始状态
fulfilled:操作成功完成
rejected:操作失败
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('Success') : reject('Error');
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
9. async/await的原理?
答案:
async/await是Promise的语法糖,基于Generator函数实现。
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
}
// 等同于
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.catch(error => console.error('Error:', error));
}
10. 箭头函数和普通函数的区别?
答案:
| 特性 | 箭头函数 | 普通函数 |
|------|---------|---------|
| this绑定 | 继承外层作用域的this | 动态绑定,取决于调用方式 |
| arguments对象 | 没有 | 有 |
| 构造函数 | 不能作为构造函数 | 可以 |
| prototype | 没有 | 有 |
| 重复命名参数 | 不允许 | 严格模式下不允许 |
| yield关键字 | 不能用作生成器 | 可以 |
11. 防抖和节流的区别与实现?
答案:
// 防抖:n秒后执行,n秒内重复触发则重新计时
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:n秒内只执行一次
function throttle(fn, delay) {
let canRun = true;
return function(...args) {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, args);
canRun = true;
}, delay);
};
}
// 使用
window.addEventListener('resize', debounce(handleResize, 200));
window.addEventListener('scroll', throttle(handleScroll, 100));
12. 什么是事件委托?
答案: 利用事件冒泡,将子元素的事件监听委托给父元素。
// 传统方式
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
// 事件委托
document.querySelector('.container').addEventListener('click', event => {
if (event.target.classList.contains('item')) {
handleClick(event);
}
});
// 动态添加元素也有效
document.querySelector('.container').insertAdjacentHTML('beforeend', '<div class="item">New</div>');
13. 跨域问题及解决方案?
答案:
同源策略:协议、域名、端口相同
解决方案:
CORS(推荐)
// 服务端设置响应头
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: Content-Type
JSONP
function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
代理服务器
WebSocket
postMessage(iframe通信)
Nginx反向代理
14. 什么是柯里化(Currying)?
答案: 将多参数函数转换为一系列单参数函数。
// 传统函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化实现
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));
};
}
};
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
15. 模块化的几种方式?
答案:
IIFE(立即执行函数表达式)
(function(global) {
const module = {};
// 私有变量
let privateVar = 'secret';
// 公有方法
module.publicMethod = function() {
return privateVar;
};
global.myModule = module;
})(window);
CommonJS(Node.js)
// module.js
module.exports = { name: 'module' };
// app.js
const module = require('./module.js');
AMD(Require.js)
define(['dependency'], function(dependency) {
return { name: 'module' };
});
ES6 Module(现代标准)
// module.js
export const name = 'module';
export default function() {};
// app.js
import { name } from './module.js';
import myFunc from './module.js';
16. 什么是尾调用优化?
答案: 函数最后一步调用另一个函数,无需保留当前函数的调用记录。
// 普通递归(会产生调用栈)
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1); // 需要等待返回值
}
// 尾递归(可优化)
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total); // 直接返回,无需等待
}
17. 内存泄漏的常见情况?
答案:
意外的全局变量
function leak() {
leakVar = 'leak'; // 没有var/let/const
this.globalVar = 'leak'; // this指向window
}
闭包使用不当
未被清理的定时器和事件监听
// 错误
setInterval(() => {}, 1000);
// 正确
const timer = setInterval(() => {}, 1000);
clearInterval(timer);
DOM引用未释放
const elements = {
button: document.getElementById('button')
};
// 即使从DOM移除,仍然在内存中
document.body.removeChild(document.getElementById('button'));
Map/Set中的对象引用
18. 如何判断数据类型?
答案:
// 1. typeof - 原始类型
typeof 'str' // 'string'
typeof 123 // 'number'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof null // 'object' (bug)
typeof [] // 'object'
typeof {} // 'object'
typeof function(){} // 'function'
// 2. instanceof - 引用类型
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true
// 3. Object.prototype.toString - 万能方法
Object.prototype.toString.call('str') // '[object String]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(null) // '[object Null]'
// 4. Array.isArray
Array.isArray([]) // true
Array.isArray({}) // false
// 封装通用方法
function getType(value) {
return Object.prototype.toString.call(value)
.slice(8, -1)
.toLowerCase();
}
19. 数组的常见方法有哪些?
答案:
改变原数组:push、pop、shift、unshift、splice、sort、reverse
不改变原数组:concat、slice、map、filter、reduce、forEach、find、findIndex、some、every
// 链式调用示例
const result = arr
.filter(item => item > 0)
.map(item => item * 2)
.reduce((sum, item) => sum + item, 0);
// flat和flatMap
const arr = [1, [2, [3]]];
arr.flat(2); // [1, 2, 3]
// 数组去重
const unique = [...new Set(arr)];
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);
20. 什么是执行上下文?
答案: 代码执行时的环境,包含变量对象、作用域链、this值。
执行上下文类型:
全局执行上下文:最外层环境
函数执行上下文:每次函数调用创建
eval执行上下文:eval代码执行时创建
执行栈:LIFO结构,管理执行上下文
21. 什么是暂时性死区(TDZ)?
答案: let/const声明的变量在声明前不可访问的区域。
console.log(a); // undefined(变量提升)
var a = 1;
console.log(b); // ReferenceError(暂时性死区)
let b = 2;
22. new操作符做了什么?
答案:
创建一个空对象
将对象的
__proto__指向构造函数的
prototype
将构造函数的
this指向新对象
执行构造函数代码
如果构造函数返回对象,则返回该对象;否则返回新对象
function myNew(constructor, ...args) {
// 1. 创建空对象
const obj = {};
// 2. 设置原型
obj.__proto__ = constructor.prototype;
// 3. 绑定this并执行
const result = constructor.apply(obj, args);
// 4. 返回结果
return result instanceof Object ? result : obj;
}
23. 实现一个发布 订阅模式?
答案:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event, listener) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(l => l !== listener);
}
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(listener => listener(...args));
}
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
// 使用
const emitter = new EventEmitter();
emitter.on('message', data => console.log(data));
emitter.emit('message', 'Hello!');
24. 函数式编程的特点?
答案:
纯函数:相同输入总是返回相同输出,无副作用
不可变性:数据不可变,创建新数据
函数是一等公民:函数可作为参数、返回值
高阶函数:接收函数作为参数或返回函数
柯里化和组合
// 纯函数
const add = (a, b) => a + b;
// 不可变性
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 不改变原数组
// 函数组合
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const add1 = x => x + 1;
const double = x => x * 2;
const add1ThenDouble = compose(double, add1);
add1ThenDouble(5); // 12
25. 什么是Web Workers?
答案: 在后台运行JavaScript的线程,不阻塞主线程。
// main.js
const worker = new Worker('worker.js');
worker.postMessage('Hello Worker');
worker.onmessage = event => {
console.log('From worker:', event.data);
};
worker.onerror = error => {
console.error('Worker error:', error);
};
// worker.js
self.onmessage = event => {
const result = doHeavyTask(event.data);
self.postMessage(result);
};
function doHeavyTask(data) {
// 复杂计算
return data.toUpperCase();
}
进阶概念
26. 什么是设计模式?常用的有哪些?
答案:
设计模式:解决特定问题的可重用方案。
常用模式:
单例模式:一个类只有一个实例
class Singleton {
static instance;
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
}
}
工厂模式:创建对象而不暴露创建逻辑
观察者模式:对象间一对多依赖关系
策略模式:定义一系列算法并使其可互换
装饰器模式:动态添加职责
27. 什么是Web Components?
答案: 创建可重用定制元素的技术,包含:
Custom Elements:定义新HTML元素
Shadow DOM:封装样式和行为
HTML Templates:定义可复用的模板
class MyElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = `
<style>
:host { display: block; }
</style>
<slot></slot>
`;
}
}
customElements.define('my-element', MyElement);
28. 性能优化方法?
答案:
代码层面
- 防抖节流
- 虚拟列表
- 懒加载
- 代码分割
- 使用Web Workers
网络层面
- HTTP缓存
- CDN
- 压缩资源(Gzip、Brotli)
- 图片优化(WebP、懒加载)
渲染层面
- 减少重绘重排
- 使用transform/opacity
- 避免强制同步布局
内存管理
- 及时清理事件监听
- 避免内存泄漏
- 使用WeakMap/WeakSet
29. 什么是Service Worker?
答案: 浏览器在后台运行的脚本,可用于离线缓存、推送通知、后台同步。
// 注册
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registered'))
.catch(error => console.log('SW registration failed'));
}
// sw.js - Service Worker文件
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll(['/', '/index.html']);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
30. 什么是Tree Shaking?
答案: 通过静态分析移除未使用的代码。
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// app.js
import { add } from './math.js'; // 只有add被包含在bundle中
31. 什么是Babel?作用是什么?
答案: JavaScript编译器,将新语法转换为旧浏览器支持的语法。
主要功能:
语法转换(ES6+ → ES5)
Polyfill(通过@babel/polyfill)
代码转换(JSX、TypeScript)
插件系统
32. 什么是Webpack?核心概念?
答案: 静态模块打包工具。
核心概念:
Entry:入口文件
Output:输出配置
Loader:处理非JavaScript文件
Plugin:扩展功能
Mode:开发/生产模式
Module:模块系统
33. 什么是虚拟DOM?
答案: 描述真实DOM的JavaScript对象,通过diff算法最小化DOM操作。
// 虚拟DOM示例
const vnode = {
tag: 'div',
props: { id: 'app', className: 'container' },
children: [
{ tag: 'h1', props: {}, children: ['Hello'] },
{ tag: 'p', props: {}, children: ['World'] }
]
};
34. 什么是MVVM模式?
答案: Model-View-ViewModel架构模式,实现数据双向绑定。
// 简单实现
class MVVM {
constructor(options) {
this.$data = options.data;
this.$el = document.querySelector(options.el);
this.observe(this.$data);
this.compile(this.$el);
}
observe(data) {
// 数据劫持
Object.keys(data).forEach(key => {
let value = data[key];
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
Dep.target && dep.addSub(Dep.target);
return value;
},
set(newVal) {
if (newVal !== value) {
value = newVal;
dep.notify();
}
}
});
});
}
compile(el) {
// 模板编译
}
}
35. 什么是SSR?优缺点?
答案: 服务端渲染(Server-Side Rendering)
优点:
更好的SEO
更快的首屏加载
更好的用户体验
缺点:
服务器压力大
开发复杂度高
TTFB可能变长
ES6+新特性
36. ES6有哪些新特性?
答案:
let/const
箭头函数
模板字符串
解构赋值
扩展运算符
默认参数
Promise
Class
Module
Set/Map
37. Map和Object的区别?
答案:
| 特性 | Map | Object |
|------|-----|--------|
| 键类型 | 任意类型 | String/Symbol |
| 键顺序 | 插入顺序 | 无序(但ES6后也有序) |
| 大小 | size属性获取 | 手动计算 |
| 迭代 | 可直接迭代 | 需要Object.keys等 |
| 性能 | 频繁增删时更好 | 一般情况 |
| 序列化 | 不能直接JSON序列化 | 可以 |
38. Set和Array的区别?
答案:
// Set:值唯一
const set = new Set([1, 2, 3, 3]); // {1, 2, 3}
set.add(4);
set.has(2); // true
// Array:可重复
const arr = [1, 2, 3, 3]; // [1, 2, 3, 3]
39. 什么是可选链操作符(?.)?
答案: 安全访问嵌套对象属性。
// 传统方式
const name = user && user.info && user.info.name;
// 可选链
const name = user?.info?.name;
// 配合空值合并运算符
const name = user?.info?.name ?? 'Default';
40. 什么是空值合并运算符(??)?
答案: 左侧为null或undefined时返回右侧值。
const value = null ?? 'default'; // 'default'
const value = 0 ?? 'default'; // 0(不同于||)
const value = '' ?? 'default'; // ''(不同于||)
41. 什么是BigInt?
答案: 表示大于2^53-1的整数。
const big = 9007199254740991n;
const big2 = BigInt('9007199254740991');
const result = big + 1n; // 9007199254740992n
42. 什么是Proxy和Reflect?
答案: Proxy用于定义基本操作的自定义行为,Reflect提供拦截JavaScript操作的方法。
const handler = {
get(target, prop) {
return prop in target ? target[prop] : 37;
},
set(target, prop, value) {
if (prop === 'age' && value < 0) {
throw new Error('Age cannot be negative');
}
target[prop] = value;
return true;
}
};
const obj = new Proxy({}, handler);
obj.age = 25;
console.log(obj.age); // 25
console.log(obj.name); // 37
43. 什么是Generator函数?
答案: 可以暂停执行的函数。
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}
// 异步应用
async function* asyncGenerator() {
const data = await fetchData();
yield data;
}
44. 什么是装饰器(Decorator)?
答案: 修改类或类成员的语法(目前Stage 3提案)。
// 类装饰器
@log
class MyClass {
@readonly
method() {}
}
function log(constructor) {
return class extends constructor {
constructor(...args) {
console.log('Creating instance');
super(...args);
}
};
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
45. ES2020+重要特性?
答案:
可选链(?.)
空值合并(??)
Promise.allSettled
动态导入(import())
globalThis
String.matchAll
BigInt
顶层await
实践题
46. 实现数组扁平化?
// 方法1: flat
const flattened = arr.flat(Infinity);
// 方法2: 递归
function flatten(arr) {
return arr.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? flatten(val) : val),
[]);
}
// 方法3: 迭代
function flatten(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
result.push(next);
}
}
return result.reverse();
}
47. 实现数组去重?
// 方法1: Set
const unique = [...new Set(arr)];
// 方法2: filter + indexOf
const unique = arr.filter((item, index) => arr.indexOf(item) === index);
// 方法3: reduce
const unique = arr.reduce((acc, item) =>
acc.includes(item) ? acc : [...acc, item],
[]);
// 方法4: 对象键值(适用于非对象类型)
const unique = Object.keys(arr.reduce((acc, item) => {
acc[item] = true;
return acc;
}, {}));
48. 实现继承的几种方式?
// 1. 原型链继承
function Parent() {}
function Child() {}
Child.prototype = new Parent();
// 2. 构造函数继承
function Parent(name) { this.name = name; }
function Child(name) { Parent.call(this, name); }
// 3. 组合继承
function Parent(name) { this.name = name; }
Parent.prototype.say = function() {};
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
// 4. 寄生组合继承(最佳)
function inherit(Child, Parent) {
const prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
// 5. Class继承
class Parent {}
class Child extends Parent {}
49. 实现Promise相关方法?
// Promise.all
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
results[index] = value;
count++;
if (count === promises.length) resolve(results);
},
reject
);
});
});
};
// Promise.race
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
Promise.resolve(promise).then(resolve, reject);
});
});
};
// Promise.allSettled
Promise.allSettled = function(promises) {
return Promise.all(
promises.map(promise =>
Promise.resolve(promise)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
};
50. 实现一个简单的Vue响应式系统?
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
const dep = getDep(target, key);
dep.depend();
return target[key];
},
set(target, key, value) {
target[key] = value;
const dep = getDep(target, key);
dep.notify();
return true;
}
});
}
// 使用
const state = reactive({ count: 0 });
watchEffect(() => {
console.log('Count:', state.count);
});
state.count++; // 自动触发watchEffect
这份总结涵盖了JavaScript面试中最常见和重要的50个问题,建议结合实际编码练习加深理解。每个问题都提供了核心概念解释和代码示例,方便学习和复习。