© 2026 考ACCA网|www.kaoacca.cn|专注前端知识体系构建
JS面试题目大全
JavaScript核心考点|高频真题详解|手写实现|性能优化|面试策略

JS面试题目大全|全面覆盖前端高频考点,助你高效备战大厂面试

本文档系统梳理JavaScript核心知识点,涵盖ES6+语法、闭包、原型链、异步编程、DOM操作、性能优化、手写代码等JS面试题目,提供详细解析、实战代码示例、高频追问策略及学习路径建议,适合初中高级前端开发者系统性复习与查漏补缺。

ES6/ES7核心语法|JS面试题目高频考点TOP 1

1.1 let/const与块级作用域

面试官常问:“var、let、const有什么区别?为什么let没有变量提升?”

⚡ 关键点

• var:函数作用域,存在变量提升(Hoisting),允许重复声明
• let/const:块级作用域(Block Scope),不存在变量提升,存在“暂时性死区”(TDZ)
• const:声明常量,必须初始化,不可重新赋值(但对象属性可变)

经典陷阱题:

// 面试高频陷阱
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出:3 3 3
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100); // 输出:0 1 2
}

追问策略:为什么let没有变量提升?—— 因为引擎在编译阶段为块级作用域创建了“词法环境”,在TDZ内访问变量会抛出ReferenceError,而非undefined。

1.2 箭头函数与this绑定

面试官常问:“箭头函数与普通函数有哪些本质区别?”

⚙️ 箭头函数四大特性

  • 无自己的this、arguments、super、new.target
  • this值由外层作用域决定(词法绑定)
  • 不能作为构造函数(无prototype)
  • 不能用作Generator(无yield)

代码示例:

const obj = {
  name: 'Alice',
  regularFunc: function() {
    setTimeout(function() {
      console.log(this.name); // window.name(非严格模式)或 undefined(严格模式)
    });
  },
  arrowFunc: function() {
    setTimeout(() => {
      console.log(this.name); // 'Alice' —— this绑定到obj
    });
  }
};
obj.regularFunc(); // undefined
obj.arrowFunc(); // 'Alice'

1.3 模板字符串与字符串方法

常用方法对比:

方法功能ES5等价
includes()判断是否包含子串indexOf(str) !== -1
startsWith()判断是否以子串开头indexOf(str) === 0
repeat(n)重复字符串n次Array(n+1).join(str)
padStart(n, str)头部补全至n长度无原生等价

⚡ 实用技巧:模板字符串嵌套

const renderList = (items) => `
  
    ${items.map(item => `
  • ${item}
  • `).join('')}
`; console.log(renderList(['JS', 'CSS', 'HTML']));

2.1 数组与对象解构

面试高频场景:从API返回的数据结构中提取字段,或处理函数参数默认值。

⚙️ 解构默认值与嵌套解构

// 嵌套解构 + 默认值
const response = {
  data: {
    user: { name: 'Bob', age: 25 },
    meta: { code: 200 }
  }
};
const {
  data: {
    user: { name, age = 18, email = 'guest@example.com' } = {},
    meta: { code } = {}
  } = {}
} = response;
console.log(name, age, email, code); // Bob 25 guest@example.com 200

2.2 函数参数解构

用于处理可选配置项,避免冗长的if判断:

function createUser({ name = 'Anonymous', role = 'user', permissions = [] } = {}) {
  return { name, role, permissions };
}
createUser(); // { name: 'Anonymous', role: 'user', permissions: [] }
createUser({ name: 'Alice', role: 'admin' }); // { name: 'Alice', role: 'admin', permissions: [] }

3.1 class语法与继承

注意:class只是语法糖,底层仍是基于原型的继承。

⚡ class与原型链对比

// class语法
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // 调用父类构造函数
  }
  speak() {
    super.speak(); // 调用父类方法
    console.log(`${this.name} barks.`);
  }
}
const d = new Dog('Rex');
d.speak(); // Rex makes a noise. / Rex barks.

底层等价实现:

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  console.log(this.name + ' makes a noise.');
};
function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
  Animal.prototype.speak.call(this);
  console.log(this.name + ' barks.');
};

4.1 ES6模块导入导出

面试易错点:导入的是值引用(live bindings),而非值拷贝。

⚡ 动态绑定示例

// counter.js
export let count = 1;
export function increment() {
  count++;
}
// main.js
import { count, increment } from './counter.js';
console.log(count); // 1
increment();
console.log(count); // 2 —— 重新导入时值已更新

对比CommonJS:CommonJS的require返回的是值拷贝,无法动态更新:

// commonjs-counter.js
let count = 1;
module.exports = { count, increment: () => count++ };
// main.js
const counter = require('./commonjs-counter');
console.log(counter.count); // 1
counter.increment();
console.log(counter.count); // 1 —— 未更新!

5. 其他重要特性

⚡ Map vs Object对比

特性ObjectMap
键类型仅字符串/Symbol任意类型
大小获取Object.keys(obj).lengthmap.size
迭代性需转换为数组原生支持迭代
原型污染风险是否

闭包与作用域|JS面试题目经典必考

1. 闭包定义与形成条件

定义:函数与其词法环境的组合,使函数能访问其词法作用域外的变量。

形成条件:

  1. 函数嵌套(内部函数)
  2. 内部函数引用了外部函数的变量
  3. 外部函数执行完毕,但内部函数仍被引用

⚡ 典型闭包案例:计数器

function createCounter() {
  let count = 0; // 被闭包引用的变量
  return function() {
    count++;
    return count;
  };
}
const counter1 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = createCounter();
console.log(counter2()); // 1 —— 独立的词法环境

2. 闭包的内存泄漏风险

当闭包引用了大型DOM节点或长生命周期对象时,可能导致内存泄漏:

// 风险写法
function setup() {
  const element = document.getElementById('btn');
  element.onclick = function() {
    console.log(element.id); // 引用了外部变量element
  };
}

解决方案:手动解除引用或使用WeakMap存储元数据

3. 作用域链与变量查找

变量查找规则:当前作用域 → 上层作用域 → ... → 全局作用域

⚡ 作用域链深度示例

function a() {
  let x = 1;
  function b() {
    let y = 2;
    function c() {
      let z = 3;
      console.log(x, y, z); // 1 2 3 —— 作用域链:c → b → a → 全局
    }
    c();
  }
  b();
}
a();

4. 闭包在模块化中的应用

ES6前常用IIFE实现模块封装:

const CounterModule = (function() {
  let count = 0; // 私有变量
  return {
    increment: () => ++count,
    decrement: () => --count,
    getValue: () => count
  };
})();
console.log(CounterModule.getValue()); // 0
CounterModule.increment();
console.log(CounterModule.getValue()); // 1

原型链与继承|JS面试题目难点解析

1. 原型链结构

⚡ 核心关系图

obj.__proto__ === Object.prototype
Object.prototype.__proto__ === null
Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype

2. 原型继承的多种方式

2.1 原型链继承

function Parent() {
  this.colors = ['red', 'blue'];
}
Parent.prototype.getName = function() {
  return this.name;
};
function Child() {}
Child.prototype = new Parent(); // 关键:父实例作为子原型
Child.prototype.constructor = Child;
const child1 = new Child();
const child2 = new Child();
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green'] —— 引用类型属性共享!

2.2 构造函数继承(借用构造函数)

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
function Child(name) {
  Parent.call(this, name); // 关键:在子构造函数中调用父构造函数
}
const child1 = new Child('Alice');
const child2 = new Child('Bob');
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue'] —— 每个实例独立副本!

缺点:无法复用方法(每个实例都有独立方法副本)

2.3 组合继承(原型链+构造函数)

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
  console.log(this.name);
};
function Child(name, age) {
  Parent.call(this, name); // 第一次调用Parent
  this.age = age;
}
Child.prototype = new Parent(); // 第二次调用Parent
Child.prototype.constructor = Child;
Child.prototype.sayAge = function() {
  console.log(this.age);
};
const child1 = new Child('Alice', 25);
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue']

缺点:调用两次父构造函数(浪费性能)

2.4 寄生组合继承(最优方案)

function inheritPrototype(child, parent) {
  const prototype = Object.create(parent.prototype); // 创建父原型副本
  prototype.constructor = child; // 修复构造函数指向
  child.prototype = prototype; // 赋值给子原型
}
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
  console.log(this.name);
};
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function() {
  console.log(this.age);
};
const child1 = new Child('Alice', 25);
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true

3. ES6 class继承本质

ES6的class ... extends底层即使用寄生组合继承:

class Animal {
  constructor(name) {
    this.name = name;
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // 调用父类构造函数
  }
}

编译后等价于:

function _inheritsLoose(subClass, superClass) {
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  subClass.__proto__ = superClass;
}
function Animal(name) { this.name = name; }
function Dog(name) {
  Animal.call(this, name);
}
_inheritsLoose(Dog, Animal);

事件循环与异步编程|JS面试题目深度解析

1. 浏览器事件循环模型

⚡ 执行栈 + 任务队列

  • Call Stack:执行同步代码
  • Task Queue(Macro-task):setTimeout、setInterval、I/O、UI渲染
  • Microtask Queue:Promise.then、MutationObserver、queueMicrotask

执行顺序:同步代码 → 所有微任务 → 渲染 → 下一个宏任务

⚡ 典型输出题

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
queueMicrotask(() => console.log('4'));
new Promise(resolve => {
  console.log('5');
  resolve();
}).then(() => console.log('6'));
console.log('7');
// 输出顺序:1 → 5 → 7 → 3 → 4 → 6 → 2

2. async/await原理

本质:基于Generator和Promise的语法糖,实现同步风格的异步代码。

⚡ async函数编译过程

// 原始代码
async function fetchData() {
  const res1 = await fetch('/api1');
  const res2 = await fetch('/api2');
  return [res1, res2];
}
// 等价实现(简化版)
function fetchData() {
  return __awaiter(this, void 0, void 0, function* () {
    const res1 = yield fetch('/api1');
    const res2 = yield fetch('/api2');
    return [res1, res2];
  });
}

3. 并发控制:Promise.all与race

⚡ 并发限制器实现

function limitConcurrent(tasks, limit) {
  const results = [];
  let index = 0;
  function run() {
    if (index >= tasks.length) return;
    const i = index++;
    tasks[i]().then(result => {
      results[i] = result;
      if (index < tasks.length) run();
    });
  }
  for (let i = 0; i < Math.min(limit, tasks.length); i++) {
    run();
  }
  return new Promise(resolve => {
    const checkComplete = () => {
      if (results.length === tasks.length) {
        resolve(results);
      } else {
        setTimeout(checkComplete, 10);
      }
    };
    checkComplete();
  });
}
// 使用示例
const tasks = [
  () => new Promise(r => setTimeout(() => r('A'), 100)),
  () => new Promise(r => setTimeout(() => r('B'), 50)),
  () => new Promise(r => setTimeout(() => r('C'), 150))
];
limitConcurrent(tasks, 2).then(console.log); // ['A', 'B', 'C'](按原顺序)

4. 实际应用场景

DOM操作与事件机制|JS面试题目实战技巧

1. DOM事件流三阶段

  1. 捕获阶段(Capture):window → document → html → body → ... → 目标元素
  2. 目标阶段(Target):事件到达目标元素
  3. 冒泡阶段(Bubble):目标元素 → ... → body → html → document → window

⚡ 事件监听参数

element.addEventListener('click', handler, {
  capture: true,      // 是否在捕获阶段触发(默认false)
  once: true,         // 仅触发一次(自动移除)
  passive: true       // 不调用preventDefault(),提升滚动性能
});

2. 事件委托(Event Delegation)

原理:利用事件冒泡,将子元素事件绑定到父元素

⚡ 典型实现

// HTML: 
  • Item 1
  • Item 2
document.getElementById('list').addEventListener('click', function(e) { if (e.target.tagName === 'LI') { console.log('Clicked:', e.target.textContent); } });

优势:

  • 减少事件监听器数量(性能优化)
  • 新添加的子元素自动拥有事件处理能力

3. 自定义事件

// 创建自定义事件
const event = new CustomEvent('dataLoaded', {
  detail: { users: ['Alice', 'Bob'] },
  bubbles: true,
  cancelable: true
});
// 监听
document.addEventListener('dataLoaded', e => {
  console.log(e.detail.users);
});
// 触发
document.dispatchEvent(event);

4. 合成事件(SyntheticEvent)

React/Vue等框架封装的跨浏览器事件系统,解决原生事件兼容性问题。

性能优化策略|JS面试题目高分要点

1. 渲染优化

⚡ 批量DOM操作示例

// 低效:每次append触发一次重排
const ul = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  ul.appendChild(li); // 1000次重排!
}
// 高效:使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
ul.appendChild(fragment); // 1次重排!

2. 内存管理

⚡ 弱引用示例

const domMap = new WeakMap();
function storeElementData(element, data) {
  domMap.set(element, data);
}
function retrieveElementData(element) {
  return domMap.get(element);
}
// 当element被移除DOM时,WeakMap自动释放引用
const el = document.getElementById('btn');
storeElementData(el, { count: 0 });
el.addEventListener('click', () => {
  const data = retrieveElementData(el);
  data.count++;
});

3. 网络优化

手写代码题库|JS面试题目实战演练

1. 防抖(Debounce)

⚡ 搜索框输入防抖

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
// 使用
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
  console.log('Searching:', e.target.value);
}, 300));

2. 节流(Throttle)

⚡ 滚动事件节流

function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
// 使用
window.addEventListener('scroll', throttle(function() {
  console.log('Scroll position:', window.scrollY);
}, 100));

3. 深拷贝(Deep Clone)

⚡ 支持循环引用的深拷贝

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (hash.has(obj)) return hash.get(obj);
  const clone = Array.isArray(obj) ? [] : {};
  hash.set(obj, clone);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], hash);
    }
  }
  return clone;
}
// 测试循环引用
const obj = { a: 1 };
obj.self = obj;
const cloned = deepClone(obj);
console.log(cloned.self === cloned); // true

4. Promise并发控制

⚡ 限制最大并发数

function limitPromise(promises, limit) {
  const results = [];
  let index = 0;
  function run() {
    if (index >= promises.length) return;
    const i = index++;
    promises[i]().then(result => {
      results[i] = result;
      run();
    });
  }
  for (let i = 0; i < Math.min(limit, promises.length); i++) {
    run();
  }
  return new Promise(resolve => {
    const check = () => {
      if (results.length === promises.length) resolve(results);
      else setTimeout(check, 10);
    };
    check();
  });
}

高频追问策略|JS面试题目高分技巧

1. 面试官追问逻辑

2. 典型追问链

问:“请解释闭包的概念”

答:闭包是函数与其词法环境的组合...

问:“闭包会导致内存泄漏吗?如何避免?”

答:当闭包引用了大型DOM节点或长生命周期对象时,可能导致内存泄漏。解决方案包括:手动解除引用、使用WeakMap存储元数据、避免不必要的闭包。

问:“如果项目中发现内存泄漏,你会如何排查?”

答:① 使用Chrome DevTools的Memory面板录制分配栈;② 检查未清除的定时器;③ 查看是否有DOM元素被JS引用;④ 分析堆快照(Heap Snapshot);⑤ 使用Lighthouse进行自动化检测。

3. 反向提问建议

浏览器原理与网络|JS面试题目关联知识

1. 浏览器渲染流程

  1. 解析HTML生成DOM Tree
  2. 解析CSS生成CSSOM Tree
  3. 合并DOM + CSSOM → Render Tree
  4. 布局(Layout):计算元素几何信息
  5. 绘制(Paint):绘制像素
  6. 合成(Composite):图层合并

⚡ 重排与重绘触发条件

操作触发重排?触发重绘?
修改元素尺寸/位置是是
修改背景色否是
修改display:none是是
使用transform否是(仅合成层)

2. HTTP缓存机制

⚡ 缓存策略对比

类型请求方式是否返回资源
强缓存命中200 (from disk cache)否
协商缓存命中304 (Not Modified)否
未命中200是

ESLint与代码规范|JS面试题目工程化实践

1. 核心规则配置

⚡ .eslintrc.json示例

{
  "extends": ["eslint:recommended", "plugin:react/recommended"],
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "no-var": "error",
    "prefer-const": "warn",
    "no-console": "off",
    "semi": ["error", "always"]
  }
}

2. 与Prettier集成

3. 提交前校验(Husky + lint-staged)

⚡ package.json配置

{
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

网友还关心的问题

问:如何准备JS面试题目才能高效通过大厂面试?

答:建议采用“三阶复习法”:

  1. 基础巩固:通读《JavaScript高级程序设计》,掌握ES6+核心特性
  2. 真题演练:刷LeetCode/牛客网前端题库,重点练习手写代码题
  3. 系统总结:整理个人知识图谱,记录常见陷阱与解决方案
问:如何判断自己JS面试题目掌握程度是否达标?

答:可参考以下自测标准:

  • 能清晰解释闭包、原型链、事件循环等核心概念
  • 能手写防抖/节流/深拷贝等基础工具函数
  • 能分析常见性能问题并提出解决方案
  • 能结合项目经验说明技术选型依据
问:前端JS面试题目中哪些是最高频考点?

答:据2026年招聘数据统计,TOP 5高频考点为:

  1. ES6+语法(let/const、箭头函数、解构)
  2. 闭包与作用域链
  3. 原型链与继承机制
  4. 事件循环与异步编程
  5. DOM操作与事件机制
斌知网
蜀ICP备2026017620号-5