ARTICLE

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. callapplybind 区别?各自应用场景?

答案:

  • call:参数逐个传递,立即执行
  • apply:参数数组传递,立即执行
  • bind:参数逐个传递,返回新函数,延迟执行,可固化 this 和参数

场景:

  • apply 用于数组批量传参(Math.max.apply(null, [1, 2, 3])
  • bind 用于定时器、事件回调固化 this
  • call 用于简单 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/finallyasync/awaitqueueMicrotaskMutationObserver(优先级高)
  • 宏任务script 整体、setTimeoutsetInterval、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.allraceallSettledany 区别和业务场景?

答案:

  • 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;
  }
}
写下你的想法

暂无评论,快来发表第一条评论吧