JS 高级面试题 20 道(含标准答案·可直接背诵)
JS 高级面试题 20 道
1. 讲讲 JS 执行上下文、作用域、作用域链、闭包的完整关系?
答案:
执行上下文是代码执行的环境容器,包含变量、作用域、this 等;作用域是变量的可访问范围;作用域链是多层作用域嵌套形成的变量查找链条,由内向外逐级查找。
闭包是内层函数引用外层函数变量,且内层函数被外部持有,导致外层执行上下文无法销毁的现象。
核心用途: 私有化变量、防抖节流、缓存数据、解决循环定时器变量问题。
弊端: 容易造成内存泄漏。
2. 闭包产生的内存泄漏如何解决?
答案:
- 手动置空闭包引用的变量、函数:赋值为
null - 及时解绑定时器、事件监听
- 避免全局长期持有闭包函数引用
- 组件卸载时销毁闭包逻辑,清空缓存
3. 讲讲 JS 变量提升、函数提升的区别?
答案:
var变量提升:提升声明,初始值为undefined,可先使用后定义,存在变量提升混乱问题let/const:存在暂时性死区,只提升声明、不初始化,无法提前访问- 函数声明:整体提升,优先级高于变量提升,可直接提前调用
- 函数表达式:遵循变量提升规则,不会整体提升
4. this 指向的五种绑定规则及优先级?
答案:
优先级从高到低: new 绑定 > 显式绑定(call / apply / bind)> 隐式绑定(对象调用)> 默认绑定(全局 / undefined)> 箭头函数绑定
- 默认绑定:普通独立函数调用,
this指向window/undefined(严格模式) - 隐式绑定:对象.函数(),
this指向调用对象 - 显式绑定:
call/apply/bind,手动指定this new绑定:构造函数调用,this指向新实例- 箭头函数:无自身
this,继承外层作用域this,不参与以上绑定
5. call、apply、bind 区别?各自应用场景?
答案:
call:参数逐个传递,立即执行apply:参数数组传递,立即执行bind:参数逐个传递,返回新函数,延迟执行,可固化this和参数
场景:
apply用于数组批量传参(Math.max.apply(null, [1, 2, 3]))bind用于定时器、事件回调固化thiscall用于简单this劫持
6. 原型、原型链、继承原理?
答案:
每个函数有 prototype 原型对象,每个实例有 __proto__ 指向构造函数 prototype;原型链是实例逐级 __proto__ 查找形成的链条,属性查找机制:自身没有则向上找原型,直到 Object.prototype,最终为 null。
JS 继承核心:子类原型指向父类实例,实现属性和方法复用,现代开发优先使用 ES6 class 继承。
7. ES6 class 是真类吗?和 ES5 构造函数区别?
答案:
class 是语法糖,底层依然是构造函数 + 原型,并非真正面向对象类。
区别: class 默认严格模式、不可枚举、必须 new 调用、方法不可遍历、支持静态属性、私有属性,语法更规范,规避 ES5 构造函数原型混乱问题。
8. 讲讲 JS 事件循环机制(宏任务、微任务)执行顺序?
答案:
完整执行顺序: 同步代码 > 清空所有微任务 > 执行一轮宏任务 > 重复循环
- 微任务:
Promise.then/catch/finally、async/await、queueMicrotask、MutationObserver(优先级高) - 宏任务:
script整体、setTimeout、setInterval、AJAX、DOM 事件(优先级低)
核心: 每一轮事件循环,微任务队列必须清空,才会执行下一个宏任务。
9. async/await 底层原理?和 Promise 的关系?
答案:
async/await 是 Promise + 生成器 Generator 的语法糖,彻底解决 Promise 链式嵌套地狱。
async 修饰函数默认返回 Promise;await 阻塞同步代码,等待 Promise 状态完成,将异步结果转为同步写法。
执行顺序: await 之后的代码全部推入微任务队列。
10. Promise 三种状态、特点、为什么状态不可逆?
答案:
三种状态: pending(等待)、fulfilled(成功)、rejected(失败)。
核心特点: 状态一旦改变永久凝固,不可逆、不可二次修改,避免异步结果多次变更导致逻辑混乱。
常用 API: then/catch/finally/all/race/allSettled/any。
11. Promise.all、race、allSettled、any 区别和业务场景?
答案:
all:全部成功才成功,一个失败直接失败(适合批量接口全部请求)race:谁先完成返回谁(适合接口超时拦截)allSettled:全部执行完毕,不管成功失败(适合批量统计结果)any:一个成功即成功,全部失败才失败(适合多接口容错降级)
12. 讲讲 JS 防抖、节流原理及区别?手写核心代码?
答案:
- 防抖:频繁触发时,延迟 n 秒执行,期间再次触发重置计时,只执行最后一次(搜索输入、窗口 resize)
- 节流:固定时间内只执行一次,限制频率(滚动、拖拽、点击防重)
核心: 防抖侧重结束后执行,节流侧重过程中限流。
javascript
// 防抖
function debounce(fn, wait) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), wait);
};
}
// 节流
function throttle(fn, wait) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= wait) {
lastTime = now;
fn.apply(this, args);
}
};
}
13. 深浅拷贝区别?手写完整深拷贝(解决循环引用)?
答案:
- 浅拷贝:只拷贝一层,引用类型共享内存,修改互相影响(
Object.assign、扩展运算符) - 深拷贝:递归拷贝所有层级,完全独立,修改互不影响
JSON.parse(JSON.stringify(obj)) 缺陷:无法拷贝函数、undefined、正则、日期、丢失循环引用;完整深拷贝需递归 + WeakMap 解决循环引用。
javascript
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (map.has(obj)) return map.get(obj);
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
14. 垃圾回收机制、V8 垃圾回收原理?
答案:
JS 自动垃圾回收,核心机制:可达性分析,无法从根对象访问的变量自动回收。
V8 分代回收:
- 新生代:副垃圾回收,复制算法,快
- 老生代:主垃圾回收,标记清除 / 标记整理
常见内存泄漏: 闭包残留、定时器未清空、事件监听未解绑、全局变量过多、DOM 引用未释放。
15. 宏任务和微任务的性能差异?为什么微任务更快?
答案:
微任务属于当前执行栈末尾,本轮代码执行完立即清空,无需等待下一轮事件循环;宏任务需要等待下一轮事件循环,存在间隔延迟。
高频异步逻辑优先用微任务,减少页面延迟、提升响应速度。
16. ES6 模块化与 CommonJS 模块化区别?
答案:
- CommonJS:运行时加载、动态导入、值拷贝、同步加载、服务端主流
- ES6 Module:编译时加载、静态解析、实时引用、支持 Tree-shaking、浏览器/Node 通用
核心: ESM 可静态优化,CommonJS 无法做编译优化。
17. 讲讲 Proxy 与 Object.defineProperty 区别?除了 Vue 响应式还有什么用途?
答案:
defineProperty 仅拦截属性读写;Proxy 拦截对象读写、删除、遍历、调用、构造等 13 种行为。
拓展用途: 表单校验、权限拦截、数据缓存、接口请求封装、全局状态监听、数据脱敏。
18. 原型链污染是什么?如何防范?
答案:
原型链污染是恶意代码修改 Object.prototype 原型属性,导致全局所有对象属性被篡改,引发全局逻辑异常。
防范:
- 冻结原型:
Object.freeze(Object.prototype) - 使用
hasOwnProperty判断属性是否自身 - 规避未知对象合并
- 过滤
__proto__、constructor字段
19. 前端常见的内存泄漏场景及解决方案?
答案:
- 全局变量滥用:尽量用局部变量、模块化作用域
- 定时器 / 监听未销毁:组件卸载手动清空
- 闭包常驻引用:手动置空引用变量
- DOM 节点移除但 JS 引用保留:清空 DOM 变量
- 缓存无限堆积:设置缓存过期、最大容量
20. 手写 Promise 核心原理,说说 Promise 解决了什么痛点?
答案:
Promise 核心: 状态管理、异步队列缓存、链式调用、状态不可逆。
解决痛点: 彻底解决回调地狱、统一异步错误处理、支持并行/串行异步控制、异步逻辑可链式复用、代码可读性大幅提升。
核心实现要点: 三种状态、resolve/reject 状态锁定、then 异步队列、链式返回新 Promise。
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (err) => {
throw err;
};
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (err) {
reject(err);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
}, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return promise2;
}
}
暂无评论,快来发表第一条评论吧